MDL-43628 mod_assign: fixed formatting of statement
[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
DW
1503 *
1504 * @return int number of submissions
1505 */
1506 public function count_submissions() {
1507 global $DB;
1508
1509 if (!$this->has_instance()) {
1510 return 0;
1511 }
1512
8f7e1c05 1513 $params = array();
b473171a
DW
1514
1515 if ($this->get_instance()->teamsubmission) {
8f7e1c05 1516 // We cannot join on the enrolment tables for group submissions (no userid).
df211804 1517 $sql = 'SELECT COUNT(DISTINCT s.groupid)
8f7e1c05
DW
1518 FROM {assign_submission} s
1519 WHERE
1520 s.assignment = :assignid AND
1521 s.timemodified IS NOT NULL AND
1522 s.userid = :groupuserid';
1523
1524 $params['assignid'] = $this->get_instance()->id;
1525 $params['groupuserid'] = 0;
1526 } else {
1527 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
c494e18c 1528 list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
8f7e1c05
DW
1529
1530 $params['assignid'] = $this->get_instance()->id;
1531
df211804 1532 $sql = 'SELECT COUNT(DISTINCT s.userid)
8f7e1c05 1533 FROM {assign_submission} s
0c7b6910 1534 JOIN(' . $esql . ') e ON e.id = s.userid
8f7e1c05
DW
1535 WHERE
1536 s.assignment = :assignid AND
1537 s.timemodified IS NOT NULL';
4c4c7b3f 1538
b473171a 1539 }
8f7e1c05 1540
b473171a
DW
1541 return $DB->count_records_sql($sql, $params);
1542 }
1543
1544 /**
e5403f8c 1545 * Load a count of submissions with a specified status.
bbd0e548
DW
1546 *
1547 * @param string $status The submission status - should match one of the constants
1548 * @return int number of matching submissions
1549 */
1550 public function count_submissions_with_status($status) {
1551 global $DB;
8f7e1c05
DW
1552
1553 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
c494e18c 1554 list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
8f7e1c05
DW
1555
1556 $params['assignid'] = $this->get_instance()->id;
df211804 1557 $params['assignid2'] = $this->get_instance()->id;
8f7e1c05 1558 $params['submissionstatus'] = $status;
12a1a0da
DW
1559
1560 if ($this->get_instance()->teamsubmission) {
8f7e1c05
DW
1561 $sql = 'SELECT COUNT(s.groupid)
1562 FROM {assign_submission} s
1563 WHERE
ad030ab4 1564 s.latest = 1 AND
8f7e1c05
DW
1565 s.assignment = :assignid AND
1566 s.timemodified IS NOT NULL AND
1567 s.userid = :groupuserid AND
1568 s.status = :submissionstatus';
1569 $params['groupuserid'] = 0;
1570 } else {
1571 $sql = 'SELECT COUNT(s.userid)
1572 FROM {assign_submission} s
0c7b6910 1573 JOIN(' . $esql . ') e ON e.id = s.userid
8f7e1c05 1574 WHERE
ad030ab4 1575 s.latest = 1 AND
8f7e1c05
DW
1576 s.assignment = :assignid AND
1577 s.timemodified IS NOT NULL AND
1578 s.status = :submissionstatus';
4c4c7b3f 1579
12a1a0da 1580 }
8f7e1c05 1581
12a1a0da 1582 return $DB->count_records_sql($sql, $params);
bbd0e548
DW
1583 }
1584
1585 /**
1586 * Utility function to get the userid for every row in the grading table
e5403f8c 1587 * so the order can be frozen while we iterate it.
bbd0e548
DW
1588 *
1589 * @return array An array of userids
1590 */
47f48152 1591 protected function get_grading_userid_list() {
bbd0e548 1592 $filter = get_user_preferences('assign_filter', '');
bf78ebd6 1593 $table = new assign_grading_table($this, 0, $filter, 0, false);
bbd0e548
DW
1594
1595 $useridlist = $table->get_column_data('userid');
1596
1597 return $useridlist;
1598 }
1599
bbd0e548 1600 /**
e5403f8c 1601 * Generate zip file from array of given files.
bbd0e548 1602 *
e5403f8c
DW
1603 * @param array $filesforzipping - array of files to pass into archive_to_pathname.
1604 * This array is indexed by the final file name and each
1605 * element in the array is an instance of a stored_file object.
1606 * @return path of temp file - note this returned file does
1607 * not have a .zip extension - it is a temp file.
bbd0e548 1608 */
47f48152 1609 protected function pack_files($filesforzipping) {
e5403f8c
DW
1610 global $CFG;
1611 // Create path for new zip file.
1612 $tempzip = tempnam($CFG->tempdir . '/', 'assignment_');
1613 // Zip files.
1614 $zipper = new zip_packer();
1615 if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1616 return $tempzip;
bbd0e548 1617 }
e5403f8c 1618 return false;
bbd0e548
DW
1619 }
1620
bbd0e548 1621 /**
3f7b501e
SH
1622 * Finds all assignment notifications that have yet to be mailed out, and mails them.
1623 *
e5403f8c 1624 * Cron function to be run periodically according to the moodle cron.
bbd0e548
DW
1625 *
1626 * @return bool
1627 */
e5403f8c 1628 public static function cron() {
3f7b501e 1629 global $DB;
75f87a57 1630
e5403f8c 1631 // Only ever send a max of one days worth of updates.
75f87a57
DW
1632 $yesterday = time() - (24 * 3600);
1633 $timenow = time();
abd0f0ae 1634 $lastcron = $DB->get_field('modules', 'lastcron', array('name' => 'assign'));
75f87a57 1635
3f7b501e 1636 // Collect all submissions from the past 24 hours that require mailing.
fdd3ebc5 1637 // Submissions are excluded if the assignment is hidden in the gradebook.
86dced43
TB
1638 $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities,
1639 g.*, g.timemodified as lastmodified, cm.id as cmid
3f7b501e
SH
1640 FROM {assign} a
1641 JOIN {assign_grades} g ON g.assignment = a.id
fdd3ebc5 1642 LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid
86dced43
TB
1643 JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id
1644 JOIN {modules} md ON md.id = cm.module AND md.name = 'assign'
fdd3ebc5
TL
1645 JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name
1646 WHERE g.timemodified >= :yesterday AND
1647 g.timemodified <= :today AND
f33be9eb
TB
1648 uf.mailed = 0 AND gri.hidden = 0
1649 ORDER BY a.course, cm.id";
e5403f8c 1650
3f7b501e
SH
1651 $params = array('yesterday' => $yesterday, 'today' => $timenow);
1652 $submissions = $DB->get_records_sql($sql, $params);
75f87a57 1653
ae7638f7 1654 if (!empty($submissions)) {
c8314005 1655
ae7638f7 1656 mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
75f87a57 1657
ae7638f7
DW
1658 // Preload courses we are going to need those.
1659 $courseids = array();
1660 foreach ($submissions as $submission) {
1661 $courseids[] = $submission->course;
1662 }
e5403f8c 1663
ae7638f7
DW
1664 // Filter out duplicates.
1665 $courseids = array_unique($courseids);
1666 $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1667 list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1668 $sql = 'SELECT c.*, ' . $ctxselect .
1669 ' FROM {course} c
1670 LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1671 WHERE c.id ' . $courseidsql;
e5403f8c 1672
ae7638f7
DW
1673 $params['contextlevel'] = CONTEXT_COURSE;
1674 $courses = $DB->get_records_sql($sql, $params);
e5403f8c 1675
ae7638f7
DW
1676 // Clean up... this could go on for a while.
1677 unset($courseids);
1678 unset($ctxselect);
1679 unset($courseidsql);
1680 unset($params);
3f7b501e 1681
ae7638f7
DW
1682 // Message students about new feedback.
1683 foreach ($submissions as $submission) {
75f87a57 1684
ae7638f7 1685 mtrace("Processing assignment submission $submission->id ...");
75f87a57 1686
ae7638f7
DW
1687 // Do not cache user lookups - could be too many.
1688 if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) {
1689 mtrace('Could not find user ' . $submission->userid);
1690 continue;
1691 }
75f87a57 1692
ae7638f7
DW
1693 // Use a cache to prevent the same DB queries happening over and over.
1694 if (!array_key_exists($submission->course, $courses)) {
1695 mtrace('Could not find course ' . $submission->course);
1696 continue;
1697 }
1698 $course = $courses[$submission->course];
1699 if (isset($course->ctxid)) {
1700 // Context has not yet been preloaded. Do so now.
1701 context_helper::preload_from_record($course);
1702 }
75f87a57 1703
ae7638f7
DW
1704 // Override the language and timezone of the "current" user, so that
1705 // mail is customised for the receiver.
1706 cron_setup_user($user, $course);
1707
1708 // Context lookups are already cached.
1709 $coursecontext = context_course::instance($course->id);
1710 if (!is_enrolled($coursecontext, $user->id)) {
1711 $courseshortname = format_string($course->shortname,
1712 true,
1713 array('context' => $coursecontext));
1714 mtrace(fullname($user) . ' not an active participant in ' . $courseshortname);
1715 continue;
1716 }
75f87a57 1717
ae7638f7
DW
1718 if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) {
1719 mtrace('Could not find grader ' . $submission->grader);
1720 continue;
1721 }
75f87a57 1722
ccd6839a 1723 $modinfo = get_fast_modinfo($course, $user->id);
86dced43 1724 $cm = $modinfo->get_cm($submission->cmid);
ae7638f7 1725 // Context lookups are already cached.
86dced43 1726 $contextmodule = context_module::instance($cm->id);
ae7638f7 1727
ccd6839a
TB
1728 if (!$cm->uservisible) {
1729 // Hold mail notification for assignments the user cannot access until later.
75f87a57
DW
1730 continue;
1731 }
bbd0e548 1732
ae7638f7
DW
1733 // Need to send this to the student.
1734 $messagetype = 'feedbackavailable';
1735 $eventtype = 'assign_notification';
1736 $updatetime = $submission->lastmodified;
1737 $modulename = get_string('modulename', 'assign');
75f87a57 1738
ae7638f7
DW
1739 $uniqueid = 0;
1740 if ($submission->blindmarking && !$submission->revealidentities) {
1741 $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
1742 }
1743 $showusers = $submission->blindmarking && !$submission->revealidentities;
1744 self::send_assignment_notification($grader,
1745 $user,
1746 $messagetype,
1747 $eventtype,
1748 $updatetime,
86dced43 1749 $cm,
ae7638f7
DW
1750 $contextmodule,
1751 $course,
1752 $modulename,
1753 $submission->name,
1754 $showusers,
1755 $uniqueid);
1756
1757 $flags = $DB->get_record('assign_user_flags', array('userid'=>$user->id, 'assignment'=>$submission->assignment));
1758 if ($flags) {
1759 $flags->mailed = 1;
1760 $DB->update_record('assign_user_flags', $flags);
1761 } else {
1762 $flags = new stdClass();
1763 $flags->userid = $user->id;
1764 $flags->assignment = $submission->assignment;
1765 $flags->mailed = 1;
1766 $DB->insert_record('assign_user_flags', $flags);
1767 }
b473171a 1768
ae7638f7 1769 mtrace('Done');
df211804 1770 }
ae7638f7 1771 mtrace('Done processing ' . count($submissions) . ' assignment submissions');
75f87a57 1772
ae7638f7
DW
1773 cron_setup_user();
1774
1775 // Free up memory just to be sure.
1776 unset($courses);
75f87a57 1777 }
75f87a57 1778
ae7638f7
DW
1779 // Update calendar events to provide a description.
1780 $sql = 'SELECT id
1781 FROM {assign}
1782 WHERE
1783 allowsubmissionsfromdate >= :lastcron AND
1784 allowsubmissionsfromdate <= :timenow AND
1785 alwaysshowdescription = 0';
1786 $params = array('lastcron' => $lastcron, 'timenow' => $timenow);
1787 $newlyavailable = $DB->get_records_sql($sql, $params);
1788 foreach ($newlyavailable as $record) {
1789 $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST);
1790 $context = context_module::instance($cm->id);
3f7b501e 1791
ae7638f7
DW
1792 $assignment = new assign($context, null, null);
1793 $assignment->update_calendar($cm->id);
1794 }
bbd0e548
DW
1795
1796 return true;
1797 }
1798
d6c673ed
DW
1799 /**
1800 * Mark in the database that this grade record should have an update notification sent by cron.
1801 *
1802 * @param stdClass $grade a grade record keyed on id
1803 * @return bool true for success
1804 */
1805 public function notify_grade_modified($grade) {
1806 global $DB;
1807
df211804
DW
1808 $flags = $this->get_user_flags($grade->userid, true);
1809 if ($flags->mailed != 1) {
1810 $flags->mailed = 0;
d6c673ed
DW
1811 }
1812
df211804
DW
1813 return $this->update_user_flags($flags);
1814 }
1815
1816 /**
1817 * Update user flags for this user in this assignment.
1818 *
1819 * @param stdClass $flags a flags record keyed on id
1820 * @return bool true for success
1821 */
1822 public function update_user_flags($flags) {
1823 global $DB;
1824 if ($flags->userid <= 0 || $flags->assignment <= 0 || $flags->id <= 0) {
1825 return false;
1826 }
1827
1828 $result = $DB->update_record('assign_user_flags', $flags);
1829 return $result;
d6c673ed
DW
1830 }
1831
bbd0e548 1832 /**
e5403f8c 1833 * Update a grade in the grade table for the assignment and in the gradebook.
bbd0e548
DW
1834 *
1835 * @param stdClass $grade a grade record keyed on id
ab38cb28 1836 * @param bool $reopenattempt If the attempt reopen method is manual, allow another attempt at this assignment.
bbd0e548
DW
1837 * @return bool true for success
1838 */
ab38cb28 1839 public function update_grade($grade, $reopenattempt = false) {
bbd0e548
DW
1840 global $DB;
1841
1842 $grade->timemodified = time();
1843
f8d107b3
DM
1844 if (!empty($grade->workflowstate)) {
1845 $validstates = $this->get_marking_workflow_states_for_current_user();
1846 if (!array_key_exists($grade->workflowstate, $validstates)) {
1847 return false;
1848 }
1849 }
1850
bbd0e548
DW
1851 if ($grade->grade && $grade->grade != -1) {
1852 if ($this->get_instance()->grade > 0) {
1853 if (!is_numeric($grade->grade)) {
1854 return false;
1855 } else if ($grade->grade > $this->get_instance()->grade) {
1856 return false;
1857 } else if ($grade->grade < 0) {
1858 return false;
1859 }
1860 } else {
e5403f8c 1861 // This is a scale.
bbd0e548
DW
1862 if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1863 $scaleoptions = make_menu_from_list($scale->scale);
1864 if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1865 return false;
1866 }
1867 }
1868 }
1869 }
1870
a8fdb36c
DW
1871 if (empty($grade->attemptnumber)) {
1872 // Set it to the default.
1873 $grade->attemptnumber = 0;
1874 }
bbd0e548 1875 $result = $DB->update_record('assign_grades', $grade);
df211804 1876
767fda9b 1877 // If the conditions are met, allow another attempt.
df211804
DW
1878 $submission = null;
1879 if ($this->get_instance()->teamsubmission) {
1880 $submission = $this->get_group_submission($grade->userid, 0, false);
1881 } else {
1882 $submission = $this->get_user_submission($grade->userid, false);
1883 }
ab38cb28
DW
1884 if ($submission && $submission->attemptnumber == $grade->attemptnumber) {
1885 $this->reopen_submission_if_required($grade->userid,
1886 $submission,
1887 $reopenattempt);
1888 }
767fda9b
GF
1889
1890 // Only push to gradebook if the update is for the latest attempt.
df211804
DW
1891 // Not the latest attempt.
1892 if ($submission && $submission->attemptnumber != $grade->attemptnumber) {
1893 return true;
1894 }
1895
bbd0e548
DW
1896 if ($result) {
1897 $this->gradebook_item_update(null, $grade);
1b90858f 1898 \mod_assign\event\submission_graded::create_from_grade($this, $grade)->trigger();
bbd0e548
DW
1899 }
1900 return $result;
1901 }
1902
9e795179 1903 /**
e5403f8c 1904 * View the grant extension date page.
9e795179
DW
1905 *
1906 * Uses url parameters 'userid'
1907 * or from parameter 'selectedusers'
e5403f8c 1908 *
9e795179
DW
1909 * @param moodleform $mform - Used for validation of the submitted data
1910 * @return string
1911 */
47f48152 1912 protected function view_grant_extension($mform) {
9e795179
DW
1913 global $DB, $CFG;
1914 require_once($CFG->dirroot . '/mod/assign/extensionform.php');
1915
1916 $o = '';
9231cd88 1917 $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
9e795179
DW
1918 $data = new stdClass();
1919 $data->extensionduedate = null;
1920 $userid = 0;
1921 if (!$batchusers) {
1922 $userid = required_param('userid', PARAM_INT);
1923
9f65f9c3 1924 $flags = $this->get_user_flags($userid, false);
9e795179
DW
1925
1926 $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
1927
9f65f9c3
DW
1928 if ($flags) {
1929 $data->extensionduedate = $flags->extensionduedate;
9e795179
DW
1930 }
1931 $data->userid = $userid;
1932 } else {
1933 $data->batchusers = $batchusers;
1934 }
e5403f8c
DW
1935 $header = new assign_header($this->get_instance(),
1936 $this->get_context(),
1937 $this->show_intro(),
1938 $this->get_course_module()->id,
1939 get_string('grantextension', 'assign'));
1940 $o .= $this->get_renderer()->render($header);
9e795179
DW
1941
1942 if (!$mform) {
e5403f8c
DW
1943 $formparams = array($this->get_course_module()->id,
1944 $userid,
1945 $batchusers,
1946 $this->get_instance(),
1947 $data);
1948 $mform = new mod_assign_extension_form(null, $formparams);
9e795179 1949 }
49d83b9d 1950 $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
9e795179
DW
1951 $o .= $this->view_footer();
1952 return $o;
1953 }
1954
12a1a0da 1955 /**
e5403f8c 1956 * Get a list of the users in the same group as this user.
12a1a0da
DW
1957 *
1958 * @param int $groupid The id of the group whose members we want or 0 for the default group
1959 * @param bool $onlyids Whether to retrieve only the user id's
1960 * @return array The users (possibly id's only)
1961 */
1962 public function get_submission_group_members($groupid, $onlyids) {
1963 $members = array();
1964 if ($groupid != 0) {
1965 if ($onlyids) {
1966 $allusers = groups_get_members($groupid, 'u.id');
1967 } else {
1968 $allusers = groups_get_members($groupid);
1969 }
1970 foreach ($allusers as $user) {
1971 if ($this->get_submission_group($user->id)) {
1972 $members[] = $user;
1973 }
1974 }
1975 } else {
1976 $allusers = $this->list_participants(null, $onlyids);
1977 foreach ($allusers as $user) {
1978 if ($this->get_submission_group($user->id) == null) {
1979 $members[] = $user;
1980 }
1981 }
1982 }
4c4c7b3f
RT
1983 // Exclude suspended users, if user can't see them.
1984 if (!has_capability('moodle/course:viewsuspendedusers', $this->context)) {
1985 foreach ($members as $key => $member) {
1ecb8044 1986 if (!$this->is_active_user($member->id)) {
4c4c7b3f
RT
1987 unset($members[$key]);
1988 }
1989 }
1990 }
12a1a0da
DW
1991 return $members;
1992 }
1993
1994 /**
e5403f8c 1995 * Get a list of the users in the same group as this user that have not submitted the assignment.
12a1a0da
DW
1996 *
1997 * @param int $groupid The id of the group whose members we want or 0 for the default group
1998 * @param bool $onlyids Whether to retrieve only the user id's
1999 * @return array The users (possibly id's only)
2000 */
2001 public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
e5403f8c
DW
2002 $instance = $this->get_instance();
2003 if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
12a1a0da
DW
2004 return array();
2005 }
2006 $members = $this->get_submission_group_members($groupid, $onlyids);
2007
2008 foreach ($members as $id => $member) {
2009 $submission = $this->get_user_submission($member->id, false);
df211804 2010 if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
12a1a0da 2011 unset($members[$id]);
88cfe469
DW
2012 } else {
2013 if ($this->is_blind_marking()) {
e5403f8c
DW
2014 $members[$id]->alias = get_string('hiddenuser', 'assign') .
2015 $this->get_uniqueid_for_user($id);
88cfe469 2016 }
12a1a0da
DW
2017 }
2018 }
2019 return $members;
2020 }
2021
2022 /**
e5403f8c 2023 * Load the group submission object for a particular user, optionally creating it if required.
12a1a0da
DW
2024 *
2025 * @param int $userid The id of the user whose submission we want
e5403f8c
DW
2026 * @param int $groupid The id of the group for this user - may be 0 in which
2027 * case it is determined from the userid.
12a1a0da 2028 * @param bool $create If set to true a new submission object will be created in the database
9e3eee67 2029 * with the status set to "new".
df211804 2030 * @param int $attemptnumber - -1 means the latest attempt
12a1a0da
DW
2031 * @return stdClass The submission
2032 */
df211804 2033 public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) {
12a1a0da
DW
2034 global $DB;
2035
2036 if ($groupid == 0) {
2037 $group = $this->get_submission_group($userid);
2038 if ($group) {
2039 $groupid = $group->id;
2040 }
2041 }
2042
12a1a0da
DW
2043 // Now get the group submission.
2044 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
df211804
DW
2045 if ($attemptnumber >= 0) {
2046 $params['attemptnumber'] = $attemptnumber;
2047 }
2048
2049 // Only return the row with the highest attemptnumber.
2050 $submission = null;
2051 $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2052 if ($submissions) {
2053 $submission = reset($submissions);
2054 }
12a1a0da
DW
2055
2056 if ($submission) {
2057 return $submission;
2058 }
2059 if ($create) {
2060 $submission = new stdClass();
e5403f8c
DW
2061 $submission->assignment = $this->get_instance()->id;
2062 $submission->userid = 0;
2063 $submission->groupid = $groupid;
12a1a0da
DW
2064 $submission->timecreated = time();
2065 $submission->timemodified = $submission->timecreated;
df211804
DW
2066 if ($attemptnumber >= 0) {
2067 $submission->attemptnumber = $attemptnumber;
12a1a0da 2068 } else {
df211804 2069 $submission->attemptnumber = 0;
12a1a0da 2070 }
1523f9e0
DW
2071 // Work out if this is the latest submission.
2072 $submission->latest = 0;
2073 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2074 if ($attemptnumber == -1) {
2075 // This is a new submission so it must be the latest.
2076 $submission->latest = 1;
2077 } else {
2078 // We need to work this out.
2079 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2080 if ($result) {
2081 $latestsubmission = reset($result);
2082 }
2083 if (!$latestsubmission || ($attemptnumber == $latestsubmission->attemptnumber)) {
2084 $submission->latest = 1;
2085 }
2086 }
2087 if ($submission->latest) {
2088 // This is the case when we need to set latest to 0 for all the other attempts.
2089 $DB->set_field('assign_submission', 'latest', 0, $params);
2090 }
9e3eee67 2091 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
12a1a0da 2092 $sid = $DB->insert_record('assign_submission', $submission);
4781ff2e 2093 return $DB->get_record('assign_submission', array('id' => $sid));
12a1a0da
DW
2094 }
2095 return false;
2096 }
2097
df47b77f 2098 /**
64220210
DW
2099 * View a summary listing of all assignments in the current course.
2100 *
2101 * @return string
2102 */
2103 private function view_course_index() {
2104 global $USER;
2105
2106 $o = '';
2107
2108 $course = $this->get_course();
2109 $strplural = get_string('modulenameplural', 'assign');
2110
2111 if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
2112 $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
2113 $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
2114 return $o;
2115 }
2116
09af1e28 2117 $strsectionname = '';
64220210
DW
2118 $usesections = course_format_uses_sections($course->format);
2119 $modinfo = get_fast_modinfo($course);
2120
2121 if ($usesections) {
09af1e28 2122 $strsectionname = get_string('sectionname', 'format_'.$course->format);
64220210
DW
2123 $sections = $modinfo->get_section_info_all();
2124 }
2125 $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
2126
2127 $timenow = time();
2128
2129 $currentsection = '';
2130 foreach ($modinfo->instances['assign'] as $cm) {
2131 if (!$cm->uservisible) {
2132 continue;
2133 }
2134
e5403f8c 2135 $timedue = $cms[$cm->id]->duedate;
64220210
DW
2136
2137 $sectionname = '';
2138 if ($usesections && $cm->sectionnum) {
2139 $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
2140 }
2141
2142 $submitted = '';
2143 $context = context_module::instance($cm->id);
2144
2145 $assignment = new assign($context, $cm, $course);
2146
2147 if (has_capability('mod/assign:grade', $context)) {
2148 $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
2149
2150 } else if (has_capability('mod/assign:submit', $context)) {
2151 $usersubmission = $assignment->get_user_submission($USER->id, false);
2152
2153 if (!empty($usersubmission->status)) {
2154 $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
2155 } else {
2156 $submitted = get_string('submissionstatus_', 'assign');
2157 }
2158 }
539af602
DW
2159 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
2160 if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
2161 !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
2162 $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
64220210
DW
2163 } else {
2164 $grade = '-';
2165 }
2166
2167 $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
2168
2169 }
2170
2171 $o .= $this->get_renderer()->render($courseindexsummary);
2172 $o .= $this->view_footer();
2173
2174 return $o;
2175 }
2176
2177 /**
2178 * View a page rendered by a plugin.
df47b77f 2179 *
e5403f8c 2180 * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
df47b77f
DW
2181 *
2182 * @return string
2183 */
47f48152 2184 protected function view_plugin_page() {
df47b77f
DW
2185 global $USER;
2186
2187 $o = '';
2188
2189 $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
2190 $plugintype = required_param('plugin', PARAM_TEXT);
2191 $pluginaction = required_param('pluginaction', PARAM_ALPHA);
2192
2193 $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
2194 if (!$plugin) {
2195 print_error('invalidformdata', '');
2196 return;
2197 }
2198
2199 $o .= $plugin->view_page($pluginaction);
2200
2201 return $o;
2202 }
2203
2204
12a1a0da
DW
2205 /**
2206 * This is used for team assignments to get the group for the specified user.
2207 * If the user is a member of multiple or no groups this will return false
2208 *
2209 * @param int $userid The id of the user whose submission we want
2210 * @return mixed The group or false
2211 */
2212 public function get_submission_group($userid) {
e5403f8c
DW
2213 $grouping = $this->get_instance()->teamsubmissiongroupingid;
2214 $groups = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
12a1a0da
DW
2215 if (count($groups) != 1) {
2216 return false;
2217 }
2218 return array_pop($groups);
2219 }
2220
9e795179 2221
bbd0e548 2222 /**
e5403f8c
DW
2223 * Display the submission that is used by a plugin.
2224 *
2225 * Uses url parameters 'sid', 'gid' and 'plugin'.
2226 *
bbd0e548
DW
2227 * @param string $pluginsubtype
2228 * @return string
2229 */
47f48152 2230 protected function view_plugin_content($pluginsubtype) {
bbd0e548
DW
2231 $o = '';
2232
2233 $submissionid = optional_param('sid', 0, PARAM_INT);
2234 $gradeid = optional_param('gid', 0, PARAM_INT);
2235 $plugintype = required_param('plugin', PARAM_TEXT);
2236 $item = null;
2237 if ($pluginsubtype == 'assignsubmission') {
2238 $plugin = $this->get_submission_plugin_by_type($plugintype);
2239 if ($submissionid <= 0) {
2240 throw new coding_exception('Submission id should not be 0');
2241 }
2242 $item = $this->get_submission($submissionid);
2243
e5403f8c 2244 // Check permissions.
4a47008c 2245 $this->require_view_submission($item->userid);
49d83b9d 2246 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bbd0e548
DW
2247 $this->get_context(),
2248 $this->show_intro(),
2249 $this->get_course_module()->id,
2250 $plugin->get_name()));
49d83b9d 2251 $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
bbd0e548
DW
2252 $item,
2253 assign_submission_plugin_submission::FULL,
2254 $this->get_course_module()->id,
2255 $this->get_return_action(),
2256 $this->get_return_params()));
2257
1be7aef2 2258 // Trigger event for viewing a submission.
1b90858f
PS
2259 \mod_assign\event\submission_viewed::create_from_submission($this, $item)->trigger();
2260
bbd0e548
DW
2261 } else {
2262 $plugin = $this->get_feedback_plugin_by_type($plugintype);
2263 if ($gradeid <= 0) {
2264 throw new coding_exception('Grade id should not be 0');
2265 }
2266 $item = $this->get_grade($gradeid);
e5403f8c 2267 // Check permissions.
4a47008c 2268 $this->require_view_submission($item->userid);
49d83b9d 2269 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bbd0e548
DW
2270 $this->get_context(),
2271 $this->show_intro(),
2272 $this->get_course_module()->id,
2273 $plugin->get_name()));
49d83b9d 2274 $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
bbd0e548
DW
2275 $item,
2276 assign_feedback_plugin_feedback::FULL,
2277 $this->get_course_module()->id,
2278 $this->get_return_action(),
2279 $this->get_return_params()));
3290c01d
MN
2280
2281 // Trigger event for viewing feedback.
1b90858f 2282 \mod_assign\event\feedback_viewed::create_from_grade($this, $item)->trigger();
bbd0e548
DW
2283 }
2284
bbd0e548
DW
2285 $o .= $this->view_return_links();
2286
2287 $o .= $this->view_footer();
1be7aef2 2288
bbd0e548
DW
2289 return $o;
2290 }
2291
2406815b
DW
2292 /**
2293 * Rewrite plugin file urls so they resolve correctly in an exported zip.
2294 *
1561a37c 2295 * @param string $text - The replacement text
2406815b
DW
2296 * @param stdClass $user - The user record
2297 * @param assign_plugin $plugin - The assignment plugin
2298 */
2299 public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
2300 $groupmode = groups_get_activity_groupmode($this->get_course_module());
2301 $groupname = '';
2302 if ($groupmode) {
2303 $groupid = groups_get_activity_group($this->get_course_module(), true);
2304 $groupname = groups_get_group_name($groupid).'-';
2305 }
2306
2307 if ($this->is_blind_marking()) {
2308 $prefix = $groupname . get_string('participant', 'assign');
2309 $prefix = str_replace('_', ' ', $prefix);
2310 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2311 } else {
2312 $prefix = $groupname . fullname($user);
2313 $prefix = str_replace('_', ' ', $prefix);
2314 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2315 }
2316
2317 $subtype = $plugin->get_subtype();
2318 $type = $plugin->get_type();
2319 $prefix = $prefix . $subtype . '_' . $type . '_';
2320
2321 $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
2322
2323 return $result;
2324 }
2325
bbd0e548 2326 /**
e5403f8c 2327 * Render the content in editor that is often used by plugin.
bbd0e548
DW
2328 *
2329 * @param string $filearea
2330 * @param int $submissionid
2331 * @param string $plugintype
2332 * @param string $editor
2333 * @param string $component
2334 * @return string
2335 */
2336 public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
2337 global $CFG;
2338
2339 $result = '';
2340
2341 $plugin = $this->get_submission_plugin_by_type($plugintype);
2342
2343 $text = $plugin->get_editor_text($editor, $submissionid);
2344 $format = $plugin->get_editor_format($editor, $submissionid);
2345
e5403f8c
DW
2346 $finaltext = file_rewrite_pluginfile_urls($text,
2347 'pluginfile.php',
2348 $this->get_context()->id,
2349 $component,
2350 $filearea,
2351 $submissionid);
2352 $params = array('overflowdiv' => true, 'context' => $this->get_context());
2353 $result .= format_text($finaltext, $format, $params);
bbd0e548
DW
2354
2355 if ($CFG->enableportfolios) {
2356 require_once($CFG->libdir . '/portfoliolib.php');
2357
2358 $button = new portfolio_add_button();
e5403f8c
DW
2359 $portfolioparams = array('cmid' => $this->get_course_module()->id,
2360 'sid' => $submissionid,
2361 'plugin' => $plugintype,
2362 'editor' => $editor,
2363 'area'=>$filearea);
2364 $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
bbd0e548
DW
2365 $fs = get_file_storage();
2366
e5403f8c
DW
2367 if ($files = $fs->get_area_files($this->context->id,
2368 $component,
2369 $filearea,
2370 $submissionid,
2371 'timemodified',
2372 false)) {
bbd0e548
DW
2373 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
2374 } else {
2375 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
2376 }
2377 $result .= $button->to_html();
2378 }
2379 return $result;
2380 }
2381
df211804 2382 /**
d9bfe3c5 2383 * Display a continue page after grading.
df211804 2384 *
d9bfe3c5 2385 * @param string $message - The message to display.
df211804
DW
2386 * @return string
2387 */
2388 protected function view_savegrading_result($message) {
2389 $o = '';
2390 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2391 $this->get_context(),
2392 $this->show_intro(),
2393 $this->get_course_module()->id,
2394 get_string('savegradingresult', 'assign')));
2395 $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'assign'),
2396 $message,
2397 $this->get_course_module()->id);
2398 $o .= $this->get_renderer()->render($gradingresult);
2399 $o .= $this->view_footer();
2400 return $o;
2401 }
bf78ebd6 2402 /**
d9bfe3c5 2403 * Display a continue page after quickgrading.
bf78ebd6 2404 *
d9bfe3c5 2405 * @param string $message - The message to display.
bf78ebd6
DW
2406 * @return string
2407 */
47f48152 2408 protected function view_quickgrading_result($message) {
bf78ebd6 2409 $o = '';
49d83b9d 2410 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bf78ebd6
DW
2411 $this->get_context(),
2412 $this->show_intro(),
2413 $this->get_course_module()->id,
2414 get_string('quickgradingresult', 'assign')));
cfdd3e5c 2415 $lastpage = optional_param('lastpage', null, PARAM_INT);
df211804
DW
2416 $gradingresult = new assign_gradingmessage(get_string('quickgradingresult', 'assign'),
2417 $message,
cfdd3e5c
GF
2418 $this->get_course_module()->id,
2419 false,
2420 $lastpage);
e5403f8c 2421 $o .= $this->get_renderer()->render($gradingresult);
bf78ebd6
DW
2422 $o .= $this->view_footer();
2423 return $o;
2424 }
bbd0e548
DW
2425
2426 /**
e5403f8c 2427 * Display the page footer.
bbd0e548 2428 *
bf78ebd6 2429 * @return string
bbd0e548 2430 */
47f48152 2431 protected function view_footer() {
1be7aef2
MN
2432 // When viewing the footer during PHPUNIT tests a set_state error is thrown.
2433 if (!PHPUNIT_TEST) {
2434 return $this->get_renderer()->render_footer();
2435 }
2436
2437 return '';
bbd0e548
DW
2438 }
2439
4a47008c
DW
2440 /**
2441 * Throw an error if the permissions to view this users submission are missing.
2442 *
2443 * @throws required_capability_exception
2444 * @return none
2445 */
2446 public function require_view_submission($userid) {
2447 if (!$this->can_view_submission($userid)) {
2448 throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2449 }
2450 }
2451
2452 /**
2453 * Throw an error if the permissions to view grades in this assignment are missing.
2454 *
2455 * @throws required_capability_exception
2456 * @return none
2457 */
2458 public function require_view_grades() {
2459 if (!$this->can_view_grades()) {
2460 throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2461 }
2462 }
2463
2464 /**
2465 * Does this user have view grade or grade permission for this assignment?
2466 *
2467 * @return bool
2468 */
2469 public function can_view_grades() {
2470 // Permissions check.
2471 if (!has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
2472 return false;
2473 }
2474
2475 return true;
2476 }
2477
bbd0e548 2478 /**
e5403f8c 2479 * Does this user have grade permission for this assignment?
bbd0e548
DW
2480 *
2481 * @return bool
2482 */
5c386472 2483 public function can_grade() {
e5403f8c 2484 // Permissions check.
bbd0e548
DW
2485 if (!has_capability('mod/assign:grade', $this->context)) {
2486 return false;
2487 }
2488
2489 return true;
2490 }
2491
2492 /**
e5403f8c 2493 * Download a zip file of all assignment submissions.
bbd0e548 2494 *
df211804 2495 * @return string - If an error occurs, this will contain the error page.
bbd0e548 2496 */
47f48152 2497 protected function download_submissions() {
e5403f8c 2498 global $CFG, $DB;
bbd0e548 2499
d0d4796b 2500 // More efficient to load this here.
bbd0e548
DW
2501 require_once($CFG->libdir.'/filelib.php');
2502
4a47008c 2503 $this->require_view_grades();
76640b27 2504
d0d4796b 2505 // Load all users with submit.
4c4c7b3f 2506 $students = get_enrolled_users($this->context, "mod/assign:submit", null, 'u.*', null, null, null,
1ecb8044 2507 $this->show_only_active_users());
bbd0e548 2508
d0d4796b 2509 // Build a list of files to zip.
bbd0e548
DW
2510 $filesforzipping = array();
2511 $fs = get_file_storage();
2512
2513 $groupmode = groups_get_activity_groupmode($this->get_course_module());
d0d4796b
DW
2514 // All users.
2515 $groupid = 0;
bbd0e548
DW
2516 $groupname = '';
2517 if ($groupmode) {
2518 $groupid = groups_get_activity_group($this->get_course_module(), true);
2519 $groupname = groups_get_group_name($groupid).'-';
2520 }
2521
d0d4796b 2522 // Construct the zip file name.
e5403f8c
DW
2523 $filename = clean_filename($this->get_course()->shortname . '-' .
2524 $this->get_instance()->name . '-' .
2525 $groupname.$this->get_course_module()->id . '.zip');
bbd0e548 2526
d0d4796b
DW
2527 // Get all the files for each student.
2528 foreach ($students as $student) {
2529 $userid = $student->id;
bbd0e548 2530
7a2b911c 2531 if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
d0d4796b 2532 // Get the plugins to add their own files to the zip.
bbd0e548 2533
d0d4796b
DW
2534 $submissiongroup = false;
2535 $groupname = '';
2536 if ($this->get_instance()->teamsubmission) {
2537 $submission = $this->get_group_submission($userid, 0, false);
2538 $submissiongroup = $this->get_submission_group($userid);
21f77397
DW
2539 if ($submissiongroup) {
2540 $groupname = $submissiongroup->name . '-';
2541 } else {
2542 $groupname = get_string('defaultteam', 'assign') . '-';
2543 }
b473171a 2544 } else {
d0d4796b 2545 $submission = $this->get_user_submission($userid, false);
b473171a 2546 }
bbd0e548 2547
b473171a 2548 if ($this->is_blind_marking()) {
e5403f8c
DW
2549 $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
2550 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
b473171a 2551 } else {
e5403f8c
DW
2552 $prefix = str_replace('_', ' ', $groupname . fullname($student));
2553 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
b473171a 2554 }
bbd0e548 2555
d0d4796b
DW
2556 if ($submission) {
2557 foreach ($this->submissionplugins as $plugin) {
2558 if ($plugin->is_enabled() && $plugin->is_visible()) {
2406815b 2559 $pluginfiles = $plugin->get_files($submission, $student);
d0d4796b 2560 foreach ($pluginfiles as $zipfilename => $file) {
7a2b911c
DW
2561 $subtype = $plugin->get_subtype();
2562 $type = $plugin->get_type();
e5403f8c
DW
2563 $prefixedfilename = clean_filename($prefix .
2564 $subtype .
2565 '_' .
2566 $type .
2567 '_' .
2568 $zipfilename);
d0d4796b
DW
2569 $filesforzipping[$prefixedfilename] = $file;
2570 }
bbd0e548
DW
2571 }
2572 }
2573 }
bbd0e548 2574 }
d0d4796b 2575 }
afa3e637 2576 $result = '';
5c778358 2577 if (count($filesforzipping) == 0) {
afa3e637
DW
2578 $header = new assign_header($this->get_instance(),
2579 $this->get_context(),
2580 '',
2581 $this->get_course_module()->id,
2582 get_string('downloadall', 'assign'));
2583 $result .= $this->get_renderer()->render($header);
5c778358 2584 $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
afa3e637
DW
2585 $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2586 'action'=>'grading'));
2587 $result .= $this->get_renderer()->continue_button($url);
5c778358 2588 $result .= $this->view_footer();
5c778358 2589 } else if ($zipfile = $this->pack_files($filesforzipping)) {
1b90858f 2590 \mod_assign\event\all_submissions_downloaded::create_from_assign($this)->trigger();
d0d4796b
DW
2591 // Send file and delete after sending.
2592 send_temp_file($zipfile, $filename);
afa3e637 2593 // We will not get here - send_temp_file calls exit.
bbd0e548 2594 }
afa3e637 2595 return $result;
bbd0e548
DW
2596 }
2597
2598 /**
e5403f8c 2599 * Util function to add a message to the log.
bbd0e548 2600 *
c17e70e5
MN
2601 * @deprecated since 2.7 - Use new events system instead.
2602 * (see http://docs.moodle.org/dev/Migrating_logging_calls_in_plugins).
2603 *
bbd0e548
DW
2604 * @param string $action The current action
2605 * @param string $info A detailed description of the change. But no more than 255 characters.
2606 * @param string $url The url to the assign module instance.
caa06f4b
FM
2607 * @param bool $return If true, returns the arguments, else adds to log. The purpose of this is to
2608 * retrieve the arguments to use them with the new event system (Event 2).
2609 * @return void|array
bbd0e548 2610 */
caa06f4b 2611 public function add_to_log($action = '', $info = '', $url='', $return = false) {
bbd0e548
DW
2612 global $USER;
2613
2614 $fullurl = 'view.php?id=' . $this->get_course_module()->id;
2615 if ($url != '') {
2616 $fullurl .= '&' . $url;
2617 }
2618
caa06f4b
FM
2619 $args = array(
2620 $this->get_course()->id,
2621 'assign',
2622 $action,
2623 $fullurl,
2624 $info,
666abe6e 2625 $this->get_course_module()->id
caa06f4b
FM
2626 );
2627
2628 if ($return) {
c17e70e5
MN
2629 // We only need to call debugging when returning a value. This is because the call to
2630 // call_user_func_array('add_to_log', $args) will trigger a debugging message of it's own.
2631 debugging('The mod_assign add_to_log() function is now deprecated.', DEBUG_DEVELOPER);
caa06f4b
FM
2632 return $args;
2633 }
2634 call_user_func_array('add_to_log', $args);
bbd0e548
DW
2635 }
2636
2cffef9f 2637 /**
e5403f8c 2638 * Lazy load the page renderer and expose the renderer to plugins.
49d83b9d 2639 *
2cffef9f
PC
2640 * @return assign_renderer
2641 */
23fffa2b 2642 public function get_renderer() {
2cffef9f
PC
2643 global $PAGE;
2644 if ($this->output) {
2645 return $this->output;
2646 }
2647 $this->output = $PAGE->get_renderer('mod_assign');
2648 return $this->output;
2649 }
bbd0e548
DW
2650
2651 /**
e5403f8c 2652 * Load the submission object for a particular user, optionally creating it if required.
bbd0e548 2653 *
12a1a0da
DW
2654 * For team assignments there are 2 submissions - the student submission and the team submission
2655 * All files are associated with the team submission but the status of the students contribution is
2656 * recorded separately.
2657 *
bbd0e548 2658 * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
e5403f8c 2659 * @param bool $create optional - defaults to false. If set to true a new submission object
9e3eee67 2660 * will be created in the database with the status set to "new".
df211804 2661 * @param int $attemptnumber - -1 means the latest attempt
bbd0e548
DW
2662 * @return stdClass The submission
2663 */
df211804 2664 public function get_user_submission($userid, $create, $attemptnumber=-1) {
bbd0e548
DW
2665 global $DB, $USER;
2666
2667 if (!$userid) {
2668 $userid = $USER->id;
2669 }
12a1a0da
DW
2670 // If the userid is not null then use userid.
2671 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
df211804
DW
2672 if ($attemptnumber >= 0) {
2673 $params['attemptnumber'] = $attemptnumber;
2674 }
2675
2676 // Only return the row with the highest attemptnumber.
2677 $submission = null;
2678 $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2679 if ($submissions) {
2680 $submission = reset($submissions);
2681 }
bbd0e548
DW
2682
2683 if ($submission) {
2684 return $submission;
2685 }
2686 if ($create) {
2687 $submission = new stdClass();
2688 $submission->assignment = $this->get_instance()->id;
2689 $submission->userid = $userid;
2690 $submission->timecreated = time();
2691 $submission->timemodified = $submission->timecreated;
9e3eee67 2692 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
df211804
DW
2693 if ($attemptnumber >= 0) {
2694 $submission->attemptnumber = $attemptnumber;
2695 } else {
2696 $submission->attemptnumber = 0;
2697 }
1523f9e0
DW
2698 // Work out if this is the latest submission.
2699 $submission->latest = 0;
2700 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2701 if ($attemptnumber == -1) {
2702 // This is a new submission so it must be the latest.
2703 $submission->latest = 1;
2704 } else {
2705 // We need to work this out.
2706 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
9e3eee67 2707 $latestsubmission = null;
1523f9e0
DW
2708 if ($result) {
2709 $latestsubmission = reset($result);
2710 }
9e3eee67 2711 if (empty($latestsubmission) || ($attemptnumber > $latestsubmission->attemptnumber)) {
1523f9e0
DW
2712 $submission->latest = 1;
2713 }
2714 }
2715 if ($submission->latest) {
2716 // This is the case when we need to set latest to 0 for all the other attempts.
2717 $DB->set_field('assign_submission', 'latest', 0, $params);
2718 }
bbd0e548 2719 $sid = $DB->insert_record('assign_submission', $submission);
4781ff2e 2720 return $DB->get_record('assign_submission', array('id' => $sid));
bbd0e548
DW
2721 }
2722 return false;
2723 }
2724
2725 /**
e5403f8c 2726 * Load the submission object from it's id.
bbd0e548
DW
2727 *
2728 * @param int $submissionid The id of the submission we want
2729 * @return stdClass The submission
2730 */
47f48152 2731 protected function get_submission($submissionid) {
bbd0e548
DW
2732 global $DB;
2733
e5403f8c
DW
2734 $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
2735 return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
bbd0e548
DW
2736 }
2737
df211804
DW
2738 /**
2739 * This will retrieve a user flags object from the db optionally creating it if required.
2740 * The user flags was split from the user_grades table in 2.5.
2741 *
2742 * @param int $userid The user we are getting the flags for.
2743 * @param bool $create If true the flags record will be created if it does not exist
2744 * @return stdClass The flags record
2745 */
2746 public function get_user_flags($userid, $create) {
2747 global $DB, $USER;
2748
2749 // If the userid is not null then use userid.
2750 if (!$userid) {
2751 $userid = $USER->id;
2752 }
2753
2754 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
2755
2756 $flags = $DB->get_record('assign_user_flags', $params);
2757
2758 if ($flags) {
2759 return $flags;
2760 }
2761 if ($create) {
2762 $flags = new stdClass();
2763 $flags->assignment = $this->get_instance()->id;
2764 $flags->userid = $userid;
2765 $flags->locked = 0;
2766 $flags->extensionduedate = 0;
f8d107b3
DM
2767 $flags->workflowstate = '';
2768 $flags->allocatedmarker = 0;
df211804
DW
2769
2770 // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
2771 // This is because students only want to be notified about certain types of update (grades and feedback).
2772 $flags->mailed = 2;
2773
2774 $fid = $DB->insert_record('assign_user_flags', $flags);
2775 $flags->id = $fid;
2776 return $flags;
2777 }
2778 return false;
2779 }
2780
bbd0e548 2781 /**
e5403f8c 2782 * This will retrieve a grade object from the db, optionally creating it if required.
bbd0e548
DW
2783 *
2784 * @param int $userid The user we are grading
2785 * @param bool $create If true the grade will be created if it does not exist
df211804 2786 * @param int $attemptnumber The attempt number to retrieve the grade for. -1 means the latest submission.
bbd0e548
DW
2787 * @return stdClass The grade record
2788 */
df211804 2789 public function get_user_grade($userid, $create, $attemptnumber=-1) {
bbd0e548
DW
2790 global $DB, $USER;
2791
df211804 2792 // If the userid is not null then use userid.
bbd0e548
DW
2793 if (!$userid) {
2794 $userid = $USER->id;
2795 }
2796
df211804 2797 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
e7af1926
DW
2798 if ($attemptnumber < 0) {
2799 // Make sure this grade matches the latest submission attempt.
2800 if ($this->get_instance()->teamsubmission) {
9e3eee67 2801 $submission = $this->get_group_submission($userid, 0, true);
e7af1926 2802 } else {
9e3eee67 2803 $submission = $this->get_user_submission($userid, true);
e7af1926
DW
2804 }
2805 if ($submission) {
2806 $attemptnumber = $submission->attemptnumber;
2807 }
2808 }
2809
df211804
DW
2810 if ($attemptnumber >= 0) {
2811 $params['attemptnumber'] = $attemptnumber;
2812 }
bbd0e548 2813
df211804
DW
2814 $grades = $DB->get_records('assign_grades', $params, 'attemptnumber DESC', '*', 0, 1);
2815
2816 if ($grades) {
2817 return reset($grades);
bbd0e548
DW
2818 }
2819 if ($create) {
2820 $grade = new stdClass();
2821 $grade->assignment = $this->get_instance()->id;
2822 $grade->userid = $userid;
2823 $grade->timecreated = time();
2824 $grade->timemodified = $grade->timecreated;
bbd0e548
DW
2825 $grade->grade = -1;
2826 $grade->grader = $USER->id;
df211804
DW
2827 if ($attemptnumber >= 0) {
2828 $grade->attemptnumber = $attemptnumber;
2829 }
d6c673ed 2830
bbd0e548
DW
2831 $gid = $DB->insert_record('assign_grades', $grade);
2832 $grade->id = $gid;
2833 return $grade;
2834 }
2835 return false;
2836 }
2837
2838 /**
e5403f8c 2839 * This will retrieve a grade object from the db.
bbd0e548
DW
2840 *
2841 * @param int $gradeid The id of the grade
2842 * @return stdClass The grade record
2843 */
47f48152 2844 protected function get_grade($gradeid) {
bbd0e548
DW
2845 global $DB;
2846
e5403f8c
DW
2847 $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
2848 return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
bbd0e548
DW
2849 }
2850
2851 /**
e5403f8c 2852 * Print the grading page for a single user submission.
bbd0e548
DW
2853 *
2854 * @param moodleform $mform
bbd0e548
DW
2855 * @return string
2856 */
d04557b3 2857 protected function view_single_grade_page($mform) {
bbd0e548
DW
2858 global $DB, $CFG;
2859
2860 $o = '';
e5403f8c 2861 $instance = $this->get_instance();
bbd0e548 2862
bbd0e548
DW
2863 require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2864
e5403f8c 2865 // Need submit permission to submit an assignment.
bbd0e548
DW
2866 require_capability('mod/assign:grade', $this->context);
2867
e5403f8c
DW
2868 $header = new assign_header($instance,
2869 $this->get_context(),
2870 false,
2871 $this->get_course_module()->id,
2872 get_string('grading', 'assign'));
2873 $o .= $this->get_renderer()->render($header);
bbd0e548 2874
df211804 2875 // If userid is passed - we are only grading a single student.
d04557b3
DW
2876 $rownum = required_param('rownum', PARAM_INT);
2877 $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
df211804
DW
2878 $userid = optional_param('userid', 0, PARAM_INT);
2879 $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
2880
d04557b3 2881 $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
df211804
DW
2882 if (!$userid) {
2883 if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
2884 $useridlist = $this->get_grading_userid_list();
2885 }
d04557b3 2886 $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
df211804
DW
2887 } else {
2888 $rownum = 0;
2889 $useridlist = array($userid);
bbd0e548 2890 }
d04557b3
DW
2891
2892 if ($rownum < 0 || $rownum > count($useridlist)) {
2893 throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
bbd0e548 2894 }
d04557b3 2895
bbd0e548
DW
2896 $last = false;
2897 $userid = $useridlist[$rownum];
2898 if ($rownum == count($useridlist) - 1) {
2899 $last = true;
2900 }
bbd0e548
DW
2901 $user = $DB->get_record('user', array('id' => $userid));
2902 if ($user) {
e5403f8c
DW
2903 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
2904 $usersummary = new assign_user_summary($user,
2905 $this->get_course()->id,
2906 $viewfullnames,
2907 $this->is_blind_marking(),
d08e6c31 2908 $this->get_uniqueid_for_user($user->id),
a69944eb 2909 get_extra_user_fields($this->get_context()),
1ecb8044 2910 !$this->is_active_user($userid));
e5403f8c 2911 $o .= $this->get_renderer()->render($usersummary);
bbd0e548 2912 }
df211804 2913 $submission = $this->get_user_submission($userid, false, $attemptnumber);
12a1a0da 2914 $submissiongroup = null;
12a1a0da
DW
2915 $teamsubmission = null;
2916 $notsubmitted = array();
e5403f8c 2917 if ($instance->teamsubmission) {
df211804 2918 $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
12a1a0da
DW
2919 $submissiongroup = $this->get_submission_group($userid);
2920 $groupid = 0;
2921 if ($submissiongroup) {
2922 $groupid = $submissiongroup->id;
2923 }
2924 $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
2925
2926 }
2927
df211804
DW
2928 // Get the requested grade.
2929 $grade = $this->get_user_grade($userid, false, $attemptnumber);
2930 $flags = $this->get_user_flags($userid, false);
bbd0e548 2931 if ($this->can_view_submission($userid)) {
df211804 2932 $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
9e795179 2933 $extensionduedate = null;
df211804
DW
2934 if ($flags) {
2935 $extensionduedate = $flags->extensionduedate;
9e795179 2936 }
88cfe469 2937 $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
12a1a0da 2938 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
9e795179 2939
e5403f8c
DW
2940 $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
2941 $instance->alwaysshowdescription,
2942 $submission,
2943 $instance->teamsubmission,
2944 $teamsubmission,
2945 $submissiongroup,
2946 $notsubmitted,
2947 $this->is_any_submission_plugin_enabled(),
2948 $gradelocked,
2949 $this->is_graded($userid),
2950 $instance->duedate,
2951 $instance->cutoffdate,
2952 $this->get_submission_plugins(),
2953 $this->get_return_action(),
2954 $this->get_return_params(),
2955 $this->get_course_module()->id,
2956 $this->get_course()->id,
2957 assign_submission_status::GRADER_VIEW,
2958 $showedit,
19195b60 2959 false,
e5403f8c
DW
2960 $viewfullnames,
2961 $extensionduedate,
2962 $this->get_context(),
2963 $this->is_blind_marking(),
df211804
DW
2964 '',
2965 $instance->attemptreopenmethod,
2966 $instance->maxattempts);
e5403f8c 2967 $o .= $this->get_renderer()->render($submissionstatus);
bbd0e548 2968 }
df211804 2969
bbd0e548
DW
2970 if ($grade) {
2971 $data = new stdClass();
e5403f8c
DW
2972 if ($grade->grade !== null && $grade->grade >= 0) {
2973 $data->grade = format_float($grade->grade, 2);
bbd0e548 2974 }
f8d107b3
DM
2975 if (!empty($flags->workflowstate)) {
2976 $data->workflowstate = $flags->workflowstate;
2977 }
2978 if (!empty($flags->allocatedmarker)) {
2979 $data->allocatedmarker = $flags->allocatedmarker;
2980 }
bbd0e548
DW
2981 } else {
2982 $data = new stdClass();
2983 $data->grade = '';
2984 }
df211804
DW
2985 // Warning if required.
2986 $allsubmissions = $this->get_all_submissions($userid);
2987
2988 if ($attemptnumber != -1) {
2989 $params = array('attemptnumber'=>$attemptnumber + 1,
2990 'totalattempts'=>count($allsubmissions));
2991 $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
2992 $o .= $this->get_renderer()->notification($message);
2993 }
bbd0e548 2994
e5403f8c 2995 // Now show the grading form.
bbd0e548 2996 if (!$mform) {
df211804
DW
2997 $pagination = array('rownum'=>$rownum,
2998 'useridlistid'=>$useridlistid,
2999 'last'=>$last,
3000 'userid'=>optional_param('userid', 0, PARAM_INT),
3001 'attemptnumber'=>$attemptnumber);
12a1a0da
DW
3002 $formparams = array($this, $data, $pagination);
3003 $mform = new mod_assign_grade_form(null,
3004 $formparams,
3005 'post',
3006 '',
3007 array('class'=>'gradeform'));
bbd0e548 3008 }
df211804 3009 $o .= $this->get_renderer()->heading(get_string('grade'), 3);
e5403f8c 3010 $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
bbd0e548 3011
df211804
DW
3012 if (count($allsubmissions) > 1 && $attemptnumber == -1) {
3013 $allgrades = $this->get_all_grades($userid);
3014 $history = new assign_attempt_history($allsubmissions,
3015 $allgrades,
3016 $this->get_submission_plugins(),
3017 $this->get_feedback_plugins(),
3018 $this->get_course_module()->id,
3019 $this->get_return_action(),
3020 $this->get_return_params(),
99758819
DW
3021 true,
3022 $useridlistid,
3023 $rownum);
df211804
DW
3024
3025 $o .= $this->get_renderer()->render($history);
3026 }
3027
1b90858f 3028 \mod_assign\event\grading_form_viewed::create_from_user($this, $user)->trigger();
bbd0e548
DW
3029
3030 $o .= $this->view_footer();
3031 return $o;
3032 }
3033
b473171a 3034 /**
e5403f8c 3035 * Show a confirmation page to make sure they want to release student identities.
b473171a
DW
3036 *
3037 * @return string
3038 */
47f48152 3039 protected function view_reveal_identities_confirm() {
b473171a
DW
3040 require_capability('mod/assign:revealidentities', $this->get_context());
3041
3042 $o = '';
e5403f8c
DW
3043 $header = new assign_header($this->get_instance(),
3044 $this->get_context(),
3045 false,
3046 $this->get_course_module()->id);
3047 $o .= $this->get_renderer()->render($header);
3048
3049 $urlparams = array('id'=>$this->get_course_module()->id,
3050 'action'=>'revealidentitiesconfirm',
3051 'sesskey'=>sesskey());
3052 $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams);
3053
3054 $urlparams = array('id'=>$this->get_course_module()->id,
3055 'action'=>'grading');
3056 $cancelurl = new moodle_url('/mod/assign/view.php', $urlparams);
3057
3058 $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'),
3059 $confirmurl,
3060 $cancelurl);
b473171a 3061 $o .= $this->view_footer();
b06decdd 3062
1b90858f 3063 \mod_assign\event\reveal_identities_confirmation_page_viewed::create_from_assign($this)->trigger();
b06decdd 3064
b473171a
DW
3065 return $o;
3066 }
3067
bbd0e548
DW
3068 /**
3069 * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
3070 *
3071 * @return string
3072 */
47f48152 3073 protected function view_return_links() {
e5403f8c
DW
3074 $returnaction = optional_param('returnaction', '', PARAM_ALPHA);
3075 $returnparams = optional_param('returnparams', '', PARAM_TEXT);
bbd0e548
DW
3076
3077 $params = array();
d04557b3 3078 $returnparams = str_replace('&amp;', '&', $returnparams);
bbd0e548 3079 parse_str($returnparams, $params);
e5403f8c
DW
3080 $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
3081 $params = array_merge($newparams, $params);
bbd0e548 3082
e5403f8c
DW
3083 $url = new moodle_url('/mod/assign/view.php', $params);
3084 return $this->get_renderer()->single_button($url, get_string('back'), 'get');
bbd0e548
DW
3085 }
3086
3087 /**
e5403f8c 3088 * View the grading table of all submissions for this assignment.
bbd0e548
DW
3089 *
3090 * @return string
3091 */
47f48152 3092 protected function view_grading_table() {
bbd0e548 3093 global $USER, $CFG;
e5403f8c
DW
3094
3095 // Include grading options form.
bbd0e548 3096 require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
bf78ebd6 3097 require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
bbd0e548
DW
3098 require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
3099 $o = '';
e5403f8c 3100 $cmid = $this->get_course_module()->id;
bbd0e548
DW
3101
3102 $links = array();
bbd0e548
DW
3103 if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
3104 has_capability('moodle/grade:viewall', $this->get_course_context())) {
a1e54f4d 3105 $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
bbd0e548
DW
3106 $links[$gradebookurl] = get_string('viewgradebook', 'assign');
3107 }
9b7a5f65 3108 if ($this->is_any_submission_plugin_enabled() && $this->count_submissions()) {
e5403f8c 3109 $downloadurl = '/mod/assign/view.php?id=' . $cmid . '&action=downloadall';
bbd0e548
DW
3110 $links[$downloadurl] = get_string('downloadall', 'assign');
3111 }
e5403f8c
DW
3112 if ($this->is_blind_marking() &&
3113 has_capability('mod/assign:revealidentities', $this->get_context())) {
3114 $revealidentitiesurl = '/mod/assign/view.php?id=' . $cmid . '&action=revealidentities';
b473171a
DW
3115 $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
3116 }
df47b77f
DW
3117 foreach ($this->get_feedback_plugins() as $plugin) {
3118 if ($plugin->is_enabled() && $plugin->is_visible()) {
3119 foreach ($plugin->get_grading_actions() as $action => $description) {
3120 $url = '/mod/assign/view.php' .
e5403f8c 3121 '?id=' . $cmid .
df47b77f
DW
3122 '&plugin=' . $plugin->get_type() .
3123 '&pluginsubtype=assignfeedback' .
3124 '&action=viewpluginpage&pluginaction=' . $action;
3125 $links[$url] = $description;
3126 }
3127 }
3128 }
a1e54f4d 3129
0d416e9c
DW
3130 // Sort links alphabetically based on the link description.
3131 core_collator::asort($links);
3132
a1e54f4d 3133 $gradingactions = new url_select($links);
49f0c151 3134 $gradingactions->set_label(get_string('choosegradingaction', 'assign'));
bbd0e548 3135
bf78ebd6
DW
3136 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
3137
79cf14b3 3138 $perpage = (int) get_user_preferences('assign_perpage', 10);
bbd0e548 3139 $filter = get_user_preferences('assign_filter', '');
f8d107b3
DM
3140 $markerfilter = get_user_preferences('assign_markerfilter', '');
3141 $workflowfilter = get_user_preferences('assign_workflowfilter', '');
bf78ebd6 3142 $controller = $gradingmanager->get_active_controller();
4a47008c 3143 $showquickgrading = empty($controller) && $this->can_grade();
bf78ebd6 3144 $quickgrading = get_user_preferences('assign_quickgrading', false);
4c4c7b3f 3145 $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
bf78ebd6 3146
5a96486b
DM
3147 $markingallocation = $this->get_instance()->markingworkflow &&
3148 $this->get_instance()->markingallocation &&
f8d107b3
DM
3149 has_capability('mod/assign:manageallocations', $this->context);
3150 // Get markers to use in drop lists.
3151 $markingallocationoptions = array();
3152 if ($markingallocation) {
3153 $markers = get_users_by_capability($this->context, 'mod/assign:grade');
3154 $markingallocationoptions[''] = get_string('filternone', 'assign');
c80d59f2 3155 $markingallocationoptions[ASSIGN_MARKER_FILTER_NO_MARKER] = get_string('markerfilternomarker', 'assign');
f8d107b3
DM
3156 foreach ($markers as $marker) {
3157 $markingallocationoptions[$marker->id] = fullname($marker);
3158 }
3159 }
3160
3161 $markingworkflow = $this->get_instance()->markingworkflow;
3162 // Get marking states to show in form.
3163 $markingworkflowoptions = array();
3164 if ($markingworkflow) {
13e82f1c 3165 $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
f8d107b3 3166 $markingworkflowoptions[''] = get_string('filternone', 'assign');
13e82f1c 3167 $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = $notma