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