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