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