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