MDL-31291: Fix a warning on the assignment summary page.
[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
29/**
30 * Assignment submission statuses
31 */
32define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft'); // student thinks it is a draft
33define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted'); // student thinks it is finished
34
35/**
36 * Search filters for grading page
37 */
38define('ASSIGN_FILTER_SUBMITTED', 'submitted');
39define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
40define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
41
bbd0e548
DW
42/** Include accesslib.php */
43require_once($CFG->libdir.'/accesslib.php');
44/** Include formslib.php */
45require_once($CFG->libdir.'/formslib.php');
46/** Include repository/lib.php */
47require_once($CFG->dirroot . '/repository/lib.php');
48/** Include local mod_form.php */
49require_once($CFG->dirroot.'/mod/assign/mod_form.php');
bbd0e548
DW
50/** gradelib.php */
51require_once($CFG->libdir.'/gradelib.php');
52/** grading lib.php */
53require_once($CFG->dirroot.'/grade/grading/lib.php');
54/** Include feedbackplugin.php */
55require_once($CFG->dirroot.'/mod/assign/feedbackplugin.php');
56/** Include submissionplugin.php */
57require_once($CFG->dirroot.'/mod/assign/submissionplugin.php');
58/** Include renderable.php */
59require_once($CFG->dirroot.'/mod/assign/renderable.php');
60/** Include gradingtable.php */
61require_once($CFG->dirroot.'/mod/assign/gradingtable.php');
62/** Include eventslib.php */
63require_once($CFG->libdir.'/eventslib.php');
64
65
66/**
67 * Standard base class for mod_assign (assignment types).
68 *
69 * @package mod_assign
70 * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
71 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
72 */
73class assign {
74
75
76 /** @var stdClass the assignment record that contains the global settings for this assign instance */
77 private $instance;
78
79 /** @var context the context of the course module for this assign instance (or just the course if we are
80 creating a new one) */
81 private $context;
82
83 /** @var stdClass the course this assign instance belongs to */
84 private $course;
bc5a657b 85
cfc81f03
DW
86 /** @var stdClass the admin config for all assign instances */
87 private $adminconfig;
88
bbd0e548
DW
89
90 /** @var assign_renderer the custom renderer for this module */
91 private $output;
92
93 /** @var stdClass the course module for this assign instance */
94 private $coursemodule;
95
96 /** @var array cache for things like the coursemodule name or the scale menu - only lives for a single
97 request */
98 private $cache;
99
100 /** @var array list of the installed submission plugins */
101 private $submissionplugins;
102
103 /** @var array list of the installed feedback plugins */
104 private $feedbackplugins;
105
106 /** @var string action to be used to return to this page (without repeating any form submissions etc.) */
107 private $returnaction = 'view';
108
109 /** @var array params to be used to return to this page */
110 private $returnparams = array();
111
112 /** @var string modulename prevents excessive calls to get_string */
f5b32abe 113 private static $modulename = null;
bbd0e548
DW
114
115 /** @var string modulenameplural prevents excessive calls to get_string */
f5b32abe 116 private static $modulenameplural = null;
bbd0e548
DW
117
118 /**
119 * Constructor for the base assign class
120 *
121 * @param mixed $coursemodulecontext context|null the course module context (or the course context if the coursemodule has not been created yet)
122 * @param mixed $coursemodule the current course module if it was already loaded - otherwise this class will load one from the context as required
123 * @param mixed $course the current course if it was already loaded - otherwise this class will load one from the context as required
124 */
125 public function __construct($coursemodulecontext, $coursemodule, $course) {
126 global $PAGE;
127
128 $this->context = $coursemodulecontext;
129 $this->coursemodule = $coursemodule;
130 $this->course = $course;
131 $this->cache = array(); // temporary cache only lives for a single request - used to reduce db lookups
132
133 $this->submissionplugins = $this->load_plugins('assignsubmission');
134 $this->feedbackplugins = $this->load_plugins('assignfeedback');
135 $this->output = $PAGE->get_renderer('mod_assign');
136 }
137
138 /**
139 * Set the action and parameters that can be used to return to the current page
140 *
141 * @param string $action The action for the current page
142 * @param array $params An array of name value pairs which form the parameters to return to the current page
143 * @return void
144 */
145 public function register_return_link($action, $params) {
146 $this->returnaction = $action;
147 $this->returnparams = $params;
148 }
149
150 /**
151 * Return an action that can be used to get back to the current page
152 * @return string action
153 */
154 public function get_return_action() {
155 return $this->returnaction;
156 }
157
158 /**
159 * Based on the current assignment settings should we display the intro
160 * @return bool showintro
161 */
162 private function show_intro() {
163 if ($this->get_instance()->alwaysshowdescription ||
164 time() > $this->get_instance()->allowsubmissionsfromdate) {
165 return true;
166 }
167 return false;
168 }
169
170 /**
171 * Return a list of parameters that can be used to get back to the current page
172 * @return array params
173 */
174 public function get_return_params() {
175 return $this->returnparams;
176 }
177
178 /**
179 * Set the submitted form data
180 * @param stdClass $data The form data (instance)
181 */
182 public function set_instance(stdClass $data) {
183 $this->instance = $data;
184 }
185
186 /**
187 * Set the context
188 * @param context $context The new context
189 */
190 public function set_context(context $context) {
191 $this->context = $context;
192 }
193
194 /**
195 * Set the course data
196 * @param stdClass $course The course data
197 */
198 public function set_course(stdClass $course) {
199 $this->course = $course;
200 }
201
202 /**
203 * get list of feedback plugins installed
204 * @return array
205 */
206 public function get_feedback_plugins() {
207 return $this->feedbackplugins;
208 }
209
210 /**
211 * get list of submission plugins installed
212 * @return array
213 */
214 public function get_submission_plugins() {
215 return $this->submissionplugins;
216 }
217
b473171a
DW
218 /**
219 * Is blind marking enabled and reveal identities not set yet?
220 *
221 * @return bool
222 */
223 public function is_blind_marking() {
224 return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
225 }
226
227 /**
228 * Does an assignment have submission(s) or grade(s) already?
229 *
230 * @return bool
231 */
232 public function has_submissions_or_grades() {
233 $allgrades = $this->count_grades();
234 $allsubmissions = $this->count_submissions();
235 if (($allgrades == 0) && ($allsubmissions == 0)) {
236 return false;
237 }
238 return true;
239 }
bbd0e548
DW
240
241 /**
242 * get a specific submission plugin by its type
243 * @param string $subtype assignsubmission | assignfeedback
244 * @param string $type
245 * @return mixed assign_plugin|null
246 */
247 private function get_plugin_by_type($subtype, $type) {
248 $shortsubtype = substr($subtype, strlen('assign'));
249 $name = $shortsubtype . 'plugins';
250 $pluginlist = $this->$name;
251 foreach ($pluginlist as $plugin) {
252 if ($plugin->get_type() == $type) {
253 return $plugin;
254 }
255 }
256 return null;
257 }
258
259 /**
260 * Get a feedback plugin by type
261 * @param string $type - The type of plugin e.g comments
262 * @return mixed assign_feedback_plugin|null
263 */
264 public function get_feedback_plugin_by_type($type) {
265 return $this->get_plugin_by_type('assignfeedback', $type);
266 }
267
268 /**
269 * Get a submission plugin by type
270 * @param string $type - The type of plugin e.g comments
271 * @return mixed assign_submission_plugin|null
272 */
273 public function get_submission_plugin_by_type($type) {
274 return $this->get_plugin_by_type('assignsubmission', $type);
275 }
276
277 /**
278 * Load the plugins from the sub folders under subtype
279 * @param string $subtype - either submission or feedback
280 * @return array - The sorted list of plugins
281 */
282 private function load_plugins($subtype) {
283 global $CFG;
284 $result = array();
285
286 $names = get_plugin_list($subtype);
287
288 foreach ($names as $name => $path) {
289 if (file_exists($path . '/locallib.php')) {
290 require_once($path . '/locallib.php');
291
292 $shortsubtype = substr($subtype, strlen('assign'));
293 $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
294
295 $plugin = new $pluginclass($this, $name);
296
297 if ($plugin instanceof assign_plugin) {
298 $idx = $plugin->get_sort_order();
299 while (array_key_exists($idx, $result)) $idx +=1;
300 $result[$idx] = $plugin;
301 }
302 }
303 }
304 ksort($result);
305 return $result;
306 }
307
308
309 /**
310 * Display the assignment, used by view.php
311 *
312 * The assignment is displayed differently depending on your role,
313 * the settings for the assignment and the status of the assignment.
314 * @param string $action The current action if any.
315 * @return void
316 */
317 public function view($action='') {
318
319 $o = '';
320 $mform = null;
321
322 // handle form submissions first
323 if ($action == 'savesubmission') {
324 $action = 'editsubmission';
325 if ($this->process_save_submission($mform)) {
326 $action = 'view';
327 }
9e795179 328 } else if ($action == 'lock') {
bbd0e548
DW
329 $this->process_lock();
330 $action = 'grading';
9e795179 331 } else if ($action == 'reverttodraft') {
bbd0e548
DW
332 $this->process_revert_to_draft();
333 $action = 'grading';
9e795179 334 } else if ($action == 'unlock') {
bbd0e548
DW
335 $this->process_unlock();
336 $action = 'grading';
9e795179 337 } else if ($action == 'confirmsubmit') {
94f26900
DW
338 $action = 'submit';
339 if ($this->process_submit_for_grading($mform)) {
340 $action = 'view';
341 }
bbd0e548 342 // save and show next button
9e795179
DW
343 } else if ($action == 'batchgradingoperation') {
344 $action = $this->process_batch_grading_operation();
345 } else if ($action == 'submitgrade') {
bbd0e548
DW
346 if (optional_param('saveandshownext', null, PARAM_ALPHA)) {
347 //save and show next
348 $action = 'grade';
349 if ($this->process_save_grade($mform)) {
350 $action = 'nextgrade';
351 }
352 } else if (optional_param('nosaveandprevious', null, PARAM_ALPHA)) {
353 $action = 'previousgrade';
354 } else if (optional_param('nosaveandnext', null, PARAM_ALPHA)) {
355 //show next button
356 $action = 'nextgrade';
357 } else if (optional_param('savegrade', null, PARAM_ALPHA)) {
358 //save changes button
359 $action = 'grade';
360 if ($this->process_save_grade($mform)) {
361 $action = 'grading';
362 }
363 } else {
364 //cancel button
365 $action = 'grading';
366 }
9e795179 367 } else if ($action == 'quickgrade') {
bf78ebd6
DW
368 $message = $this->process_save_quick_grades();
369 $action = 'quickgradingresult';
9e795179 370 } else if ($action == 'saveoptions') {
bbd0e548
DW
371 $this->process_save_grading_options();
372 $action = 'grading';
9e795179
DW
373 } else if ($action == 'saveextension') {
374 $action = 'grantextension';
375 if ($this->process_save_extension($mform)) {
376 $action = 'grading';
377 }
b473171a
DW
378 } else if ($action == 'revealidentitiesconfirm') {
379 $this->process_reveal_identities();
380 $action = 'grading';
bbd0e548
DW
381 }
382
383 $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT));
384 $this->register_return_link($action, $returnparams);
385
386 // now show the right view page
387 if ($action == 'previousgrade') {
388 $mform = null;
389 $o .= $this->view_single_grade_page($mform, -1);
bf78ebd6
DW
390 } else if ($action == 'quickgradingresult') {
391 $mform = null;
392 $o .= $this->view_quickgrading_result($message);
bbd0e548
DW
393 } else if ($action == 'nextgrade') {
394 $mform = null;
395 $o .= $this->view_single_grade_page($mform, 1);
bbd0e548
DW
396 } else if ($action == 'grade') {
397 $o .= $this->view_single_grade_page($mform);
398 } else if ($action == 'viewpluginassignfeedback') {
399 $o .= $this->view_plugin_content('assignfeedback');
400 } else if ($action == 'viewpluginassignsubmission') {
401 $o .= $this->view_plugin_content('assignsubmission');
402 } else if ($action == 'editsubmission') {
403 $o .= $this->view_edit_submission_page($mform);
404 } else if ($action == 'grading') {
405 $o .= $this->view_grading_page();
406 } else if ($action == 'downloadall') {
407 $o .= $this->download_submissions();
408 } else if ($action == 'submit') {
94f26900 409 $o .= $this->check_submit_for_grading($mform);
9e795179
DW
410 } else if ($action == 'grantextension') {
411 $o .= $this->view_grant_extension($mform);
b473171a
DW
412 } else if ($action == 'revealidentities') {
413 $o .= $this->view_reveal_identities_confirm($mform);
bbd0e548
DW
414 } else {
415 $o .= $this->view_submission_page();
416 }
417
418 return $o;
419 }
420
421
422 /**
423 * Add this instance to the database
424 *
425 * @param stdClass $formdata The data submitted from the form
426 * @param bool $callplugins This is used to skip the plugin code
427 * when upgrading an old assignment to a new one (the plugins get called manually)
428 * @return mixed false if an error occurs or the int id of the new instance
429 */
430 public function add_instance(stdClass $formdata, $callplugins) {
431 global $DB;
432
433 $err = '';
434
435 // add the database record
436 $update = new stdClass();
437 $update->name = $formdata->name;
438 $update->timemodified = time();
439 $update->timecreated = time();
440 $update->course = $formdata->course;
441 $update->courseid = $formdata->course;
442 $update->intro = $formdata->intro;
443 $update->introformat = $formdata->introformat;
444 $update->alwaysshowdescription = $formdata->alwaysshowdescription;
bbd0e548 445 $update->submissiondrafts = $formdata->submissiondrafts;
94f26900 446 $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
bbd0e548 447 $update->sendnotifications = $formdata->sendnotifications;
75f87a57 448 $update->sendlatenotifications = $formdata->sendlatenotifications;
bbd0e548 449 $update->duedate = $formdata->duedate;
9e795179 450 $update->cutoffdate = $formdata->cutoffdate;
bbd0e548
DW
451 $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
452 $update->grade = $formdata->grade;
694b11ab 453 $update->completionsubmit = !empty($formdata->completionsubmit);
12a1a0da
DW
454 $update->teamsubmission = $formdata->teamsubmission;
455 $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
456 $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
b473171a 457 $update->blindmarking = $formdata->blindmarking;
12a1a0da 458
bbd0e548
DW
459 $returnid = $DB->insert_record('assign', $update);
460 $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
461 // cache the course record
462 $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
463
464 if ($callplugins) {
465 // call save_settings hook for submission plugins
466 foreach ($this->submissionplugins as $plugin) {
467 if (!$this->update_plugin_instance($plugin, $formdata)) {
468 print_error($plugin->get_error());
469 return false;
470 }
471 }
472 foreach ($this->feedbackplugins as $plugin) {
473 if (!$this->update_plugin_instance($plugin, $formdata)) {
474 print_error($plugin->get_error());
475 return false;
476 }
477 }
478
479 // in the case of upgrades the coursemodule has not been set so we need to wait before calling these two
480 // TODO: add event to the calendar
481 $this->update_calendar($formdata->coursemodule);
482 // TODO: add the item in the gradebook
483 $this->update_gradebook(false, $formdata->coursemodule);
484
485 }
486
487 $update = new stdClass();
488 $update->id = $this->get_instance()->id;
489 $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
490 $DB->update_record('assign', $update);
491
492 return $returnid;
493 }
494
495 /**
496 * Delete all grades from the gradebook for this assignment
497 *
498 * @return bool
499 */
500 private function delete_grades() {
501 global $CFG;
502
503 return grade_update('mod/assign', $this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, 0, NULL, array('deleted'=>1)) == GRADE_UPDATE_OK;
504 }
505
506 /**
507 * Delete this instance from the database
508 *
509 * @return bool false if an error occurs
510 */
511 public function delete_instance() {
512 global $DB;
513 $result = true;
514
515 foreach ($this->submissionplugins as $plugin) {
516 if (!$plugin->delete_instance()) {
517 print_error($plugin->get_error());
518 $result = false;
519 }
520 }
521 foreach ($this->feedbackplugins as $plugin) {
522 if (!$plugin->delete_instance()) {
523 print_error($plugin->get_error());
524 $result = false;
525 }
526 }
527
528 // delete files associated with this assignment
529 $fs = get_file_storage();
530 if (! $fs->delete_area_files($this->context->id) ) {
531 $result = false;
532 }
533
534 // delete_records will throw an exception if it fails - so no need for error checking here
535
536 $DB->delete_records('assign_submission', array('assignment'=>$this->get_instance()->id));
537 $DB->delete_records('assign_grades', array('assignment'=>$this->get_instance()->id));
538 $DB->delete_records('assign_plugin_config', array('assignment'=>$this->get_instance()->id));
539
540 // delete items from the gradebook
541 if (! $this->delete_grades()) {
542 $result = false;
543 }
544
545 // delete the instance
546 $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
547
548 return $result;
549 }
550
551 /**
552 * Update the settings for a single plugin
553 *
554 * @param assign_plugin $plugin The plugin to update
555 * @param stdClass $formdata The form data
556 * @return bool false if an error occurs
557 */
558 private function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
559 if ($plugin->is_visible()) {
560 $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
561 if ($formdata->$enabledname) {
562 $plugin->enable();
563 if (!$plugin->save_settings($formdata)) {
564 print_error($plugin->get_error());
565 return false;
566 }
567 } else {
568 $plugin->disable();
569 }
570 }
571 return true;
572 }
573
574 /**
575 * Update the gradebook information for this assignment
576 *
577 * @param bool $reset If true, will reset all grades in the gradbook for this assignment
578 * @param int $coursemoduleid This is required because it might not exist in the database yet
579 * @return bool
580 */
581 public function update_gradebook($reset, $coursemoduleid) {
582 global $CFG;
583 /** Include lib.php */
584 require_once($CFG->dirroot.'/mod/assign/lib.php');
585 $assign = clone $this->get_instance();
586 $assign->cmidnumber = $coursemoduleid;
587 $param = null;
588 if ($reset) {
589 $param = 'reset';
590 }
591
592 return assign_grade_item_update($assign, $param);
593 }
594
cfc81f03 595 /** Load and cache the admin config for this module
bc5a657b 596 *
cfc81f03
DW
597 * @return stdClass the plugin config
598 */
599 public function get_admin_config() {
600 if ($this->adminconfig) {
601 return $this->adminconfig;
602 }
b11808c7 603 $this->adminconfig = get_config('assign');
cfc81f03
DW
604 return $this->adminconfig;
605 }
606
bbd0e548
DW
607
608 /**
609 * Update the calendar entries for this assignment
610 *
611 * @param int $coursemoduleid - Required to pass this in because it might not exist in the database yet
612 * @return bool
613 */
614 public function update_calendar($coursemoduleid) {
615 global $DB, $CFG;
616 require_once($CFG->dirroot.'/calendar/lib.php');
617
618 // special case for add_instance as the coursemodule has not been set yet.
619
620 if ($this->get_instance()->duedate) {
621 $event = new stdClass();
622
623 if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id))) {
624
625 $event->name = $this->get_instance()->name;
626
627 $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
628 $event->timestart = $this->get_instance()->duedate;
629
630 $calendarevent = calendar_event::load($event->id);
631 $calendarevent->update($event);
632 } else {
633 $event = new stdClass();
634 $event->name = $this->get_instance()->name;
635 $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
636 $event->courseid = $this->get_instance()->course;
637 $event->groupid = 0;
638 $event->userid = 0;
639 $event->modulename = 'assign';
640 $event->instance = $this->get_instance()->id;
641 $event->eventtype = 'due';
642 $event->timestart = $this->get_instance()->duedate;
643 $event->timeduration = 0;
644
645 calendar_event::create($event);
646 }
647 } else {
648 $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id));
649 }
650 }
651
652
653 /**
654 * Update this instance in the database
655 *
656 * @param stdClass $formdata - the data submitted from the form
657 * @return bool false if an error occurs
658 */
659 public function update_instance($formdata) {
660 global $DB;
661
662 $update = new stdClass();
663 $update->id = $formdata->instance;
664 $update->name = $formdata->name;
665 $update->timemodified = time();
666 $update->course = $formdata->course;
667 $update->intro = $formdata->intro;
668 $update->introformat = $formdata->introformat;
669 $update->alwaysshowdescription = $formdata->alwaysshowdescription;
bbd0e548 670 $update->submissiondrafts = $formdata->submissiondrafts;
94f26900 671 $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
bbd0e548 672 $update->sendnotifications = $formdata->sendnotifications;
75f87a57 673 $update->sendlatenotifications = $formdata->sendlatenotifications;
bbd0e548 674 $update->duedate = $formdata->duedate;
9e795179 675 $update->cutoffdate = $formdata->cutoffdate;
bbd0e548
DW
676 $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
677 $update->grade = $formdata->grade;
694b11ab 678 $update->completionsubmit = !empty($formdata->completionsubmit);
12a1a0da
DW
679 $update->teamsubmission = $formdata->teamsubmission;
680 $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
681 $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
b473171a 682 $update->blindmarking = $formdata->blindmarking;
12a1a0da 683
bbd0e548
DW
684
685 $result = $DB->update_record('assign', $update);
686 $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
687
688 // load the assignment so the plugins have access to it
689
690 // call save_settings hook for submission plugins
691 foreach ($this->submissionplugins as $plugin) {
692 if (!$this->update_plugin_instance($plugin, $formdata)) {
693 print_error($plugin->get_error());
694 return false;
695 }
696 }
697 foreach ($this->feedbackplugins as $plugin) {
698 if (!$this->update_plugin_instance($plugin, $formdata)) {
699 print_error($plugin->get_error());
700 return false;
701 }
702 }
703
704
705 // update the database record
706
707
708 // update all the calendar events
709 $this->update_calendar($this->get_course_module()->id);
710
711 $this->update_gradebook(false, $this->get_course_module()->id);
712
713 $update = new stdClass();
714 $update->id = $this->get_instance()->id;
715 $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
716 $DB->update_record('assign', $update);
717
718
719
720
721
722 return $result;
723 }
724
725 /**
726 * add elements in grading plugin form
727 *
728 * @param mixed $grade stdClass|null
729 * @param MoodleQuickForm $mform
730 * @param stdClass $data
731 * @return void
732 */
733 private function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data) {
734 foreach ($this->feedbackplugins as $plugin) {
735 if ($plugin->is_enabled() && $plugin->is_visible()) {
736 $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
737 if (!$plugin->get_form_elements($grade, $mform, $data)) {
738 $mform->removeElement('header_' . $plugin->get_type());
739 }
740 }
741 }
742 }
743
744
745
746 /**
747 * Add one plugins settings to edit plugin form
748 *
749 * @param assign_plugin $plugin The plugin to add the settings from
750 * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
751 * @return void
752 */
753 private function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform) {
754 global $CFG;
755 if ($plugin->is_visible()) {
756 // enabled
757 //tied disableIf rule to this select element
758 $mform->addElement('selectyesno', $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $plugin->get_name());
759 $mform->addHelpButton($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', 'enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
760
bbd0e548 761
cfc81f03 762 $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
bbd0e548
DW
763 if ($plugin->get_config('enabled') !== false) {
764 $default = $plugin->is_enabled();
765 }
766 $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
767
768 $plugin->get_settings($mform);
769
770 }
771
772 }
773
774
775 /**
776 * Add settings to edit plugin form
777 *
778 * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
779 * @return void
780 */
781 public function add_all_plugin_settings(MoodleQuickForm $mform) {
782 $mform->addElement('header', 'general', get_string('submissionsettings', 'assign'));
783
784 foreach ($this->submissionplugins as $plugin) {
785 $this->add_plugin_settings($plugin, $mform);
786
787 }
788 $mform->addElement('header', 'general', get_string('feedbacksettings', 'assign'));
789 foreach ($this->feedbackplugins as $plugin) {
790 $this->add_plugin_settings($plugin, $mform);
791 }
792 }
793
794 /**
795 * Allow each plugin an opportunity to update the defaultvalues
796 * passed in to the settings form (needed to set up draft areas for
797 * editor and filemanager elements)
798 * @param array $defaultvalues
799 */
800 public function plugin_data_preprocessing(&$defaultvalues) {
801 foreach ($this->submissionplugins as $plugin) {
802 if ($plugin->is_visible()) {
803 $plugin->data_preprocessing($defaultvalues);
804 }
805 }
806 foreach ($this->feedbackplugins as $plugin) {
807 if ($plugin->is_visible()) {
808 $plugin->data_preprocessing($defaultvalues);
809 }
810 }
811 }
812
813 /**
814 * Get the name of the current module.
815 *
816 * @return string the module name (Assignment)
817 */
818 protected function get_module_name() {
819 if (isset(self::$modulename)) {
820 return self::$modulename;
821 }
822 self::$modulename = get_string('modulename', 'assign');
823 return self::$modulename;
824 }
825
826 /**
827 * Get the plural name of the current module.
828 *
829 * @return string the module name plural (Assignments)
830 */
831 protected function get_module_name_plural() {
832 if (isset(self::$modulenameplural)) {
833 return self::$modulenameplural;
834 }
835 self::$modulenameplural = get_string('modulenameplural', 'assign');
836 return self::$modulenameplural;
837 }
838
839 /**
840 * Has this assignment been constructed from an instance?
841 *
842 * @return bool
843 */
844 public function has_instance() {
845 return $this->instance || $this->get_course_module();
846 }
847
848 /**
849 * Get the settings for the current instance of this assignment
850 *
851 * @return stdClass The settings
852 */
853 public function get_instance() {
854 global $DB;
855 if ($this->instance) {
856 return $this->instance;
857 }
858 if ($this->get_course_module()) {
859 $this->instance = $DB->get_record('assign', array('id' => $this->get_course_module()->instance), '*', MUST_EXIST);
860 }
861 if (!$this->instance) {
862 throw new coding_exception('Improper use of the assignment class. Cannot load the assignment record.');
863 }
864 return $this->instance;
865 }
866
867 /**
868 * Get the context of the current course
869 * @return mixed context|null The course context
870 */
871 public function get_course_context() {
872 if (!$this->context && !$this->course) {
873 throw new coding_exception('Improper use of the assignment class. Cannot load the course context.');
874 }
875 if ($this->context) {
876 return $this->context->get_course_context();
877 } else {
878 return context_course::instance($this->course->id);
879 }
880 }
881
882
883 /**
884 * Get the current course module
885 *
886 * @return mixed stdClass|null The course module
887 */
888 public function get_course_module() {
889 if ($this->coursemodule) {
890 return $this->coursemodule;
891 }
892 if (!$this->context) {
893 return null;
894 }
895
896 if ($this->context->contextlevel == CONTEXT_MODULE) {
897 $this->coursemodule = get_coursemodule_from_id('assign', $this->context->instanceid, 0, false, MUST_EXIST);
898 return $this->coursemodule;
899 }
900 return null;
901 }
902
903 /**
904 * Get context module
905 *
906 * @return context
907 */
908 public function get_context() {
909 return $this->context;
910 }
911
912 /**
913 * Get the current course
914 * @return mixed stdClass|null The course
915 */
916 public function get_course() {
917 global $DB;
918 if ($this->course) {
919 return $this->course;
920 }
921
922 if (!$this->context) {
923 return null;
924 }
925 $this->course = $DB->get_record('course', array('id' => $this->get_course_context()->instanceid), '*', MUST_EXIST);
926 return $this->course;
927 }
928
929 /**
930 * Return a grade in user-friendly form, whether it's a scale or not
931 *
9682626e 932 * @param mixed $grade int|null
bf78ebd6 933 * @param boolean $editing Are we allowing changes to this grade?
2a4fbc32
SH
934 * @param int $userid The user id the grade belongs to
935 * @param int $modified Timestamp from when the grade was last modified
bbd0e548
DW
936 * @return string User-friendly representation of grade
937 */
bf78ebd6 938 public function display_grade($grade, $editing, $userid=0, $modified=0) {
bbd0e548
DW
939 global $DB;
940
941 static $scalegrades = array();
942
2a4fbc32
SH
943 if ($this->get_instance()->grade >= 0) {
944 // Normal number
e7ade405 945 if ($editing && $this->get_instance()->grade > 0) {
2d8a9ce9
DW
946 if ($grade < 0) {
947 $displaygrade = '';
948 } else {
949 $displaygrade = format_float($grade);
950 }
502be9a0 951 $o = '<input type="text" name="quickgrade_' . $userid . '" value="' . $displaygrade . '" size="6" maxlength="10" class="quickgrade"/>';
bf78ebd6 952 $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade,2);
2a4fbc32 953 $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
bf78ebd6 954 return $o;
bbd0e548 955 } else {
a1e54f4d
DW
956 if ($grade == -1 || $grade === null) {
957 return '-';
958 } else {
959 return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
960 }
bbd0e548
DW
961 }
962
2a4fbc32
SH
963 } else {
964 // Scale
bbd0e548
DW
965 if (empty($this->cache['scale'])) {
966 if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
967 $this->cache['scale'] = make_menu_from_list($scale->scale);
968 } else {
969 return '-';
970 }
971 }
bf78ebd6
DW
972 if ($editing) {
973 $o = '<select name="quickgrade_' . $userid . '" class="quickgrade">';
974 $o .= '<option value="-1">' . get_string('nograde') . '</option>';
975 foreach ($this->cache['scale'] as $optionid => $option) {
976 $selected = '';
977 if ($grade == $optionid) {
978 $selected = 'selected="selected"';
979 }
980 $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
981 }
982 $o .= '</select>';
983 $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
984 return $o;
985 } else {
986 $scaleid = (int)$grade;
987 if (isset($this->cache['scale'][$scaleid])) {
988 return $this->cache['scale'][$scaleid];
989 }
990 return '-';
bbd0e548 991 }
bbd0e548
DW
992 }
993 }
994
995 /**
996 * Load a list of users enrolled in the current course with the specified permission and group (0 for no group)
997 *
998 * @param int $currentgroup
999 * @param bool $idsonly
1000 * @return array List of user records
1001 */
1002 public function list_participants($currentgroup, $idsonly) {
1003 if ($idsonly) {
1004 return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup, 'u.id');
1005 } else {
1006 return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
1007 }
1008 }
1009
12a1a0da
DW
1010 /**
1011 * Load a count of valid teams for this assignment
1012 *
1013 * @return int number of valid teams
1014 */
1015 public function count_teams() {
1016
1017 $groups = groups_get_all_groups($this->get_course()->id, 0, $this->get_instance()->teamsubmissiongroupingid, 'g.id');
1018 $count = count($groups);
1019
1020 // See if there are any users in the default group.
1021 $defaultusers = $this->get_submission_group_members(0, true);
1022 if (count($defaultusers) > 0) {
1023 $count += 1;
1024 }
1025 return $count;
1026 }
1027
bbd0e548
DW
1028 /**
1029 * Load a count of users enrolled in the current course with the specified permission and group (0 for no group)
1030 *
1031 * @param int $currentgroup
1032 * @return int number of matching users
1033 */
1034 public function count_participants($currentgroup) {
1035 return count_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
1036 }
1037
f70079b9
DW
1038 /**
1039 * Load a count of users submissions in the current module that require grading
1040 * This means the submission modification time is more recent than the
1041 * grading modification time.
1042 *
1043 * @return int number of matching submissions
1044 */
1045 public function count_submissions_need_grading() {
1046 global $DB;
1047
1048 $params = array($this->get_course_module()->instance);
1049
1050 return $DB->count_records_sql("SELECT COUNT('x')
1051 FROM {assign_submission} s
1052 LEFT JOIN {assign_grades} g ON s.assignment = g.assignment AND s.userid = g.userid
1053 WHERE s.assignment = ?
1054 AND s.timemodified IS NOT NULL
1055 AND (s.timemodified > g.timemodified OR g.timemodified IS NULL)",
1056 $params);
1057 }
1058
bbd0e548 1059 /**
b473171a
DW
1060 * Load a count of grades
1061 *
1062 * @return int number of grades
1063 */
1064 public function count_grades() {
1065 global $DB;
1066
1067 if (!$this->has_instance()) {
1068 return 0;
1069 }
1070
1071 $sql = 'SELECT COUNT(id) FROM {assign_grades} WHERE assignment = ?';
1072 $params = array($this->get_course_module()->instance);
1073
1074 return $DB->count_records_sql($sql, $params);
1075 }
1076
1077 /**
1078 * Load a count of submissions
1079 *
1080 * @return int number of submissions
1081 */
1082 public function count_submissions() {
1083 global $DB;
1084
1085 if (!$this->has_instance()) {
1086 return 0;
1087 }
1088
1089 $sql = 'SELECT COUNT(id) FROM {assign_submission} WHERE assignment = ?';
1090 $params = array($this->get_course_module()->instance);
1091
1092 if ($this->get_instance()->teamsubmission) {
1093 // only look at team submissions
1094 $sql .= ' AND userid = ?';
1095 $params[] = 0;
1096 }
1097 return $DB->count_records_sql($sql, $params);
1098 }
1099
1100 /**
1101 * Load a count of submissions with a specified status
bbd0e548
DW
1102 *
1103 * @param string $status The submission status - should match one of the constants
1104 * @return int number of matching submissions
1105 */
1106 public function count_submissions_with_status($status) {
1107 global $DB;
12a1a0da
DW
1108 $sql = 'SELECT COUNT(id) FROM {assign_submission} WHERE assignment = ? AND status = ?';
1109 $params = array($this->get_course_module()->instance, $status);
1110
1111 if ($this->get_instance()->teamsubmission) {
1112 // only look at team submissions
1113 $sql .= ' AND userid = ?';
1114 $params[] = 0;
1115 }
1116 return $DB->count_records_sql($sql, $params);
bbd0e548
DW
1117 }
1118
1119 /**
1120 * Utility function to get the userid for every row in the grading table
1121 * so the order can be frozen while we iterate it
1122 *
1123 * @return array An array of userids
1124 */
12a1a0da 1125 private function get_grading_userid_list() {
bbd0e548 1126 $filter = get_user_preferences('assign_filter', '');
bf78ebd6 1127 $table = new assign_grading_table($this, 0, $filter, 0, false);
bbd0e548
DW
1128
1129 $useridlist = $table->get_column_data('userid');
1130
1131 return $useridlist;
1132 }
1133
1134
1135 /**
1136 * Utility function get the userid based on the row number of the grading table.
1137 * This takes into account any active filters on the table.
1138 *
1139 * @param int $num The row number of the user
1140 * @param bool $last This is set to true if this is the last user in the table
1141 * @return mixed The user id of the matching user or false if there was an error
1142 */
12a1a0da 1143 private function get_userid_for_row($num, $last) {
bbd0e548
DW
1144 if (!array_key_exists('userid_for_row', $this->cache)) {
1145 $this->cache['userid_for_row'] = array();
1146 }
1147 if (array_key_exists($num, $this->cache['userid_for_row'])) {
1148 list($userid, $last) = $this->cache['userid_for_row'][$num];
1149 return $userid;
1150 }
1151
1152 $filter = get_user_preferences('assign_filter', '');
bf78ebd6 1153 $table = new assign_grading_table($this, 0, $filter, 0, false);
bbd0e548
DW
1154
1155 $userid = $table->get_cell_data($num, 'userid', $last);
1156
1157 $this->cache['userid_for_row'][$num] = array($userid, $last);
1158 return $userid;
1159 }
1160
1161 /**
1162 * Return all assignment submissions by ENROLLED students (even empty)
1163 *
1164 * @param string $sort optional field names for the ORDER BY in the sql query
1165 * @param string $dir optional specifying the sort direction, defaults to DESC
1166 * @return array The submission objects indexed by id
1167 */
1168 private function get_all_submissions( $sort="", $dir="DESC") {
1169 global $CFG, $DB;
1170
1171 if ($sort == "lastname" or $sort == "firstname") {
1172 $sort = "u.$sort $dir";
1173 } else if (empty($sort)) {
1174 $sort = "a.timemodified DESC";
1175 } else {
1176 $sort = "a.$sort $dir";
1177 }
1178
1179 return $DB->get_records_sql("SELECT a.*
1180 FROM {assign_submission} a, {user} u
1181 WHERE u.id = a.userid
1182 AND a.assignment = ?
1183 ORDER BY $sort", array($this->get_instance()->id));
1184
1185 }
1186
1187 /**
1188 * Generate zip file from array of given files
1189 *
1190 * @param array $filesforzipping - array of files to pass into archive_to_pathname - this array is indexed by the final file name and each element in the array is an instance of a stored_file object
1191 * @return path of temp file - note this returned file does not have a .zip extension - it is a temp file.
1192 */
1193 private function pack_files($filesforzipping) {
1194 global $CFG;
1195 //create path for new zip file.
1196 $tempzip = tempnam($CFG->tempdir.'/', 'assignment_');
1197 //zip files
1198 $zipper = new zip_packer();
1199 if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1200 return $tempzip;
1201 }
1202 return false;
1203 }
1204
bbd0e548 1205 /**
3f7b501e
SH
1206 * Finds all assignment notifications that have yet to be mailed out, and mails them.
1207 *
1208 * Cron function to be run periodically according to the moodle cron
bbd0e548
DW
1209 *
1210 * @return bool
1211 */
1212 static function cron() {
3f7b501e 1213 global $DB;
75f87a57
DW
1214
1215 // only ever send a max of one days worth of updates
1216 $yesterday = time() - (24 * 3600);
1217 $timenow = time();
1218
3f7b501e 1219 // Collect all submissions from the past 24 hours that require mailing.
b473171a
DW
1220 $sql = "SELECT s.*, a.course, a.name, a.blindmarking, a.revealidentities,
1221 g.*, g.id as gradeid, g.timemodified as lastmodified
3f7b501e
SH
1222 FROM {assign} a
1223 JOIN {assign_grades} g ON g.assignment = a.id
1224 LEFT JOIN {assign_submission} s ON s.assignment = a.id AND s.userid = g.userid
1225 WHERE g.timemodified >= :yesterday AND
1226 g.timemodified <= :today AND
1227 g.mailed = 0";
1228 $params = array('yesterday' => $yesterday, 'today' => $timenow);
1229 $submissions = $DB->get_records_sql($sql, $params);
75f87a57 1230
c8314005
SH
1231 if (empty($submissions)) {
1232 mtrace('done.');
1233 return true;
1234 }
1235
75f87a57
DW
1236 mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
1237
3f7b501e
SH
1238 // Preload courses we are going to need those.
1239 $courseids = array();
1240 foreach ($submissions as $submission) {
1241 $courseids[] = $submission->course;
1242 }
1243 // Filter out duplicates
1244 $courseids = array_unique($courseids);
1245 $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1246 list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1247 $sql = "SELECT c.*, {$ctxselect}
1248 FROM {course} c
1249 LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1250 WHERE c.id {$courseidsql}";
1251 $params['contextlevel'] = CONTEXT_COURSE;
1252 $courses = $DB->get_records_sql($sql, $params);
1253 // Clean up... this could go on for a while.
1254 unset($courseids);
1255 unset($ctxselect);
1256 unset($courseidsql);
1257 unset($params);
1258
1259 // Simple array we'll use for caching modules.
1260 $modcache = array();
1261
c1d09c6f 1262 // Message students about new feedback
75f87a57
DW
1263 foreach ($submissions as $submission) {
1264
1265 mtrace("Processing assignment submission $submission->id ...");
1266
1267 // do not cache user lookups - could be too many
3f7b501e 1268 if (!$user = $DB->get_record("user", array("id"=>$submission->userid))) {
75f87a57
DW
1269 mtrace("Could not find user $submission->userid");
1270 continue;
1271 }
1272
1273 // use a cache to prevent the same DB queries happening over and over
3f7b501e
SH
1274 if (!array_key_exists($submission->course, $courses)) {
1275 mtrace("Could not find course $submission->course");
1276 continue;
1277 }
1278 $course = $courses[$submission->course];
1279 if (isset($course->ctxid)) {
1280 // Context has not yet been preloaded. Do so now.
1281 context_helper::preload_from_record($course);
75f87a57
DW
1282 }
1283
3f7b501e
SH
1284 // Override the language and timezone of the "current" user, so that
1285 // mail is customised for the receiver.
75f87a57
DW
1286 cron_setup_user($user, $course);
1287
1288 // context lookups are already cached
3f7b501e 1289 $coursecontext = context_course::instance($course->id);
75f87a57 1290 if (!is_enrolled($coursecontext, $user->id)) {
3f7b501e 1291 $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
75f87a57
DW
1292 mtrace(fullname($user)." not an active participant in " . $courseshortname);
1293 continue;
1294 }
1295
3f7b501e 1296 if (!$grader = $DB->get_record("user", array("id"=>$submission->grader))) {
75f87a57
DW
1297 mtrace("Could not find grader $submission->grader");
1298 continue;
1299 }
1300
3f7b501e 1301 if (!array_key_exists($submission->assignment, $modcache)) {
75f87a57
DW
1302 if (! $mod = get_coursemodule_from_instance("assign", $submission->assignment, $course->id)) {
1303 mtrace("Could not find course module for assignment id $submission->assignment");
1304 continue;
1305 }
3f7b501e
SH
1306 $modcache[$submission->assignment] = $mod;
1307 } else {
1308 $mod = $modcache[$submission->assignment];
75f87a57 1309 }
75f87a57
DW
1310 // context lookups are already cached
1311 $contextmodule = context_module::instance($mod->id);
bbd0e548 1312
3f7b501e
SH
1313 if (!$mod->visible) {
1314 // Hold mail notification for hidden assignments until later
75f87a57
DW
1315 continue;
1316 }
1317
1318 // need to send this to the student
3f7b501e 1319 $messagetype = 'feedbackavailable';
f750cf71 1320 $eventtype = 'assign_notification';
3f7b501e
SH
1321 $updatetime = $submission->lastmodified;
1322 $modulename = get_string('modulename', 'assign');
b473171a
DW
1323
1324 $uniqueid = 0;
1325 if ($submission->blindmarking && !$submission->revealidentities) {
1326 $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
1327 }
1328 self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime,
1329 $mod, $contextmodule, $course, $modulename, $submission->name,
1330 $submission->blindmarking && !$submission->revealidentities,
1331 $uniqueid);
75f87a57
DW
1332
1333 $grade = new stdClass();
1334 $grade->id = $submission->gradeid;
1335 $grade->mailed = 1;
1336 $DB->update_record('assign_grades', $grade);
1337
1338 mtrace('Done');
1339 }
1340 mtrace('Done processing ' . count($submissions) . ' assignment submissions');
1341
1342 cron_setup_user();
3f7b501e
SH
1343
1344 // Free up memory just to be sure
1345 unset($courses);
1346 unset($modcache);
bbd0e548
DW
1347
1348 return true;
1349 }
1350
1351 /**
1352 * Update a grade in the grade table for the assignment and in the gradebook
1353 *
1354 * @param stdClass $grade a grade record keyed on id
1355 * @return bool true for success
1356 */
1357 private function update_grade($grade) {
1358 global $DB;
1359
1360 $grade->timemodified = time();
1361
1362 if ($grade->grade && $grade->grade != -1) {
1363 if ($this->get_instance()->grade > 0) {
1364 if (!is_numeric($grade->grade)) {
1365 return false;
1366 } else if ($grade->grade > $this->get_instance()->grade) {
1367 return false;
1368 } else if ($grade->grade < 0) {
1369 return false;
1370 }
1371 } else {
1372 // this is a scale
1373 if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1374 $scaleoptions = make_menu_from_list($scale->scale);
1375 if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1376 return false;
1377 }
1378 }
1379 }
1380 }
1381
1382 $result = $DB->update_record('assign_grades', $grade);
1383 if ($result) {
1384 $this->gradebook_item_update(null, $grade);
1385 }
1386 return $result;
1387 }
1388
9e795179
DW
1389 /**
1390 * View the grant extension date page
1391 *
1392 * Uses url parameters 'userid'
1393 * or from parameter 'selectedusers'
1394 * @param moodleform $mform - Used for validation of the submitted data
1395 * @return string
1396 */
1397 private function view_grant_extension($mform) {
1398 global $DB, $CFG;
1399 require_once($CFG->dirroot . '/mod/assign/extensionform.php');
1400
1401 $o = '';
1402 $batchusers = optional_param('selectedusers', '', PARAM_TEXT);
1403 $data = new stdClass();
1404 $data->extensionduedate = null;
1405 $userid = 0;
1406 if (!$batchusers) {
1407 $userid = required_param('userid', PARAM_INT);
1408
1409 $grade = $this->get_user_grade($userid, false);
1410
1411 $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
1412
1413 if ($grade) {
1414 $data->extensionduedate = $grade->extensionduedate;
1415 }
1416 $data->userid = $userid;
1417 } else {
1418 $data->batchusers = $batchusers;
1419 }
1420 $o .= $this->output->render(new assign_header($this->get_instance(),
1421 $this->get_context(),
1422 $this->show_intro(),
1423 $this->get_course_module()->id,
1424 get_string('grantextension', 'assign')));
1425
1426 if (!$mform) {
1427 $mform = new mod_assign_extension_form(null, array($this->get_course_module()->id,
1428 $userid,
1429 $batchusers,
1430 $this->get_instance(),
1431 $data));
1432 }
1433 $o .= $this->output->render(new assign_form('extensionform', $mform));
1434 $o .= $this->view_footer();
1435 return $o;
1436 }
1437
12a1a0da
DW
1438 /**
1439 * Get a list of the users in the same group as this user
1440 *
1441 * @param int $groupid The id of the group whose members we want or 0 for the default group
1442 * @param bool $onlyids Whether to retrieve only the user id's
1443 * @return array The users (possibly id's only)
1444 */
1445 public function get_submission_group_members($groupid, $onlyids) {
1446 $members = array();
1447 if ($groupid != 0) {
1448 if ($onlyids) {
1449 $allusers = groups_get_members($groupid, 'u.id');
1450 } else {
1451 $allusers = groups_get_members($groupid);
1452 }
1453 foreach ($allusers as $user) {
1454 if ($this->get_submission_group($user->id)) {
1455 $members[] = $user;
1456 }
1457 }
1458 } else {
1459 $allusers = $this->list_participants(null, $onlyids);
1460 foreach ($allusers as $user) {
1461 if ($this->get_submission_group($user->id) == null) {
1462 $members[] = $user;
1463 }
1464 }
1465 }
1466 return $members;
1467 }
1468
1469 /**
1470 * Get a list of the users in the same group as this user that have not submitted the assignment
1471 *
1472 * @param int $groupid The id of the group whose members we want or 0 for the default group
1473 * @param bool $onlyids Whether to retrieve only the user id's
1474 * @return array The users (possibly id's only)
1475 */
1476 public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
1477 if (!$this->get_instance()->teamsubmission || !$this->get_instance()->requireallteammemberssubmit) {
1478 return array();
1479 }
1480 $members = $this->get_submission_group_members($groupid, $onlyids);
1481
1482 foreach ($members as $id => $member) {
1483 $submission = $this->get_user_submission($member->id, false);
1484 if ($submission && $submission->status != ASSIGN_SUBMISSION_STATUS_DRAFT) {
1485 unset($members[$id]);
88cfe469
DW
1486 } else {
1487 if ($this->is_blind_marking()) {
1488 $members[$id]->alias = get_string('hiddenuser', 'assign') . $this->get_uniqueid_for_user($id);
1489 }
12a1a0da
DW
1490 }
1491 }
1492 return $members;
1493 }
1494
1495 /**
1496 * Load the group submission object for a particular user, optionally creating it if required
1497 *
1498 * This will create the user submission and the group submission if required
1499 *
1500 * @param int $userid The id of the user whose submission we want
1501 * @param int $groupid The id of the group for this user - may be 0 in which case it is determined from the userid
1502 * @param bool $create If set to true a new submission object will be created in the database
1503 * @return stdClass The submission
1504 */
1505 public function get_group_submission($userid, $groupid, $create) {
1506 global $DB;
1507
1508 if ($groupid == 0) {
1509 $group = $this->get_submission_group($userid);
1510 if ($group) {
1511 $groupid = $group->id;
1512 }
1513 }
1514
1515 if ($create) {
1516 // Make sure there is a submission for this user.
1517 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>0, 'userid'=>$userid);
1518 $submission = $DB->get_record('assign_submission', $params);
1519
1520 if (!$submission) {
1521 $submission = new stdClass();
1522 $submission->assignment = $this->get_instance()->id;
1523 $submission->userid = $userid;
1524 $submission->groupid = 0;
1525 $submission->timecreated = time();
1526 $submission->timemodified = $submission->timecreated;
1527
1528 if ($this->get_instance()->submissiondrafts) {
1529 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1530 } else {
1531 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1532 }
1533 $DB->insert_record('assign_submission', $submission);
1534 }
1535 }
1536 // Now get the group submission.
1537 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
1538 $submission = $DB->get_record('assign_submission', $params);
1539
1540 if ($submission) {
1541 return $submission;
1542 }
1543 if ($create) {
1544 $submission = new stdClass();
1545 $submission->assignment = $this->get_instance()->id;
1546 $submission->userid = 0;
1547 $submission->groupid = $groupid;
1548 $submission->timecreated = time();
1549 $submission->timemodified = $submission->timecreated;
1550
1551 if ($this->get_instance()->submissiondrafts) {
1552 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1553 } else {
1554 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1555 }
1556 $sid = $DB->insert_record('assign_submission', $submission);
1557 $submission->id = $sid;
1558 return $submission;
1559 }
1560 return false;
1561 }
1562
1563 /**
1564 * This is used for team assignments to get the group for the specified user.
1565 * If the user is a member of multiple or no groups this will return false
1566 *
1567 * @param int $userid The id of the user whose submission we want
1568 * @return mixed The group or false
1569 */
1570 public function get_submission_group($userid) {
1571 $groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_instance()->teamsubmissiongroupingid);
1572 if (count($groups) != 1) {
1573 return false;
1574 }
1575 return array_pop($groups);
1576 }
1577
9e795179 1578
bbd0e548
DW
1579 /**
1580 * display the submission that is used by a plugin
1581 * Uses url parameters 'sid', 'gid' and 'plugin'
1582 * @param string $pluginsubtype
1583 * @return string
1584 */
1585 private function view_plugin_content($pluginsubtype) {
1586 global $USER;
1587
1588 $o = '';
1589
1590 $submissionid = optional_param('sid', 0, PARAM_INT);
1591 $gradeid = optional_param('gid', 0, PARAM_INT);
1592 $plugintype = required_param('plugin', PARAM_TEXT);
1593 $item = null;
1594 if ($pluginsubtype == 'assignsubmission') {
1595 $plugin = $this->get_submission_plugin_by_type($plugintype);
1596 if ($submissionid <= 0) {
1597 throw new coding_exception('Submission id should not be 0');
1598 }
1599 $item = $this->get_submission($submissionid);
1600
1601 // permissions
1602 if ($item->userid != $USER->id) {
1603 require_capability('mod/assign:grade', $this->context);
1604 }
1605 $o .= $this->output->render(new assign_header($this->get_instance(),
1606 $this->get_context(),
1607 $this->show_intro(),
1608 $this->get_course_module()->id,
1609 $plugin->get_name()));
1610 $o .= $this->output->render(new assign_submission_plugin_submission($plugin,
1611 $item,
1612 assign_submission_plugin_submission::FULL,
1613 $this->get_course_module()->id,
1614 $this->get_return_action(),
1615 $this->get_return_params()));
1616
1617 $this->add_to_log('view submission', get_string('viewsubmissionforuser', 'assign', $item->userid));
1618 } else {
1619 $plugin = $this->get_feedback_plugin_by_type($plugintype);
1620 if ($gradeid <= 0) {
1621 throw new coding_exception('Grade id should not be 0');
1622 }
1623 $item = $this->get_grade($gradeid);
1624 // permissions
1625 if ($item->userid != $USER->id) {
1626 require_capability('mod/assign:grade', $this->context);
1627 }
1628 $o .= $this->output->render(new assign_header($this->get_instance(),
1629 $this->get_context(),
1630 $this->show_intro(),
1631 $this->get_course_module()->id,
1632 $plugin->get_name()));
1633 $o .= $this->output->render(new assign_feedback_plugin_feedback($plugin,
1634 $item,
1635 assign_feedback_plugin_feedback::FULL,
1636 $this->get_course_module()->id,
1637 $this->get_return_action(),
1638 $this->get_return_params()));
1639 $this->add_to_log('view feedback', get_string('viewfeedbackforuser', 'assign', $item->userid));
1640 }
1641
1642
1643 $o .= $this->view_return_links();
1644
1645 $o .= $this->view_footer();
1646 return $o;
1647 }
1648
1649 /**
1650 * render the content in editor that is often used by plugin
1651 *
1652 * @param string $filearea
1653 * @param int $submissionid
1654 * @param string $plugintype
1655 * @param string $editor
1656 * @param string $component
1657 * @return string
1658 */
1659 public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
1660 global $CFG;
1661
1662 $result = '';
1663
1664 $plugin = $this->get_submission_plugin_by_type($plugintype);
1665
1666 $text = $plugin->get_editor_text($editor, $submissionid);
1667 $format = $plugin->get_editor_format($editor, $submissionid);
1668
1669 $finaltext = file_rewrite_pluginfile_urls($text, 'pluginfile.php', $this->get_context()->id, $component, $filearea, $submissionid);
1670 $result .= format_text($finaltext, $format, array('overflowdiv' => true, 'context' => $this->get_context()));
1671
1672
1673
1674 if ($CFG->enableportfolios) {
1675 require_once($CFG->libdir . '/portfoliolib.php');
1676
1677 $button = new portfolio_add_button();
1678 $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->get_course_module()->id, 'sid' => $submissionid, 'plugin' => $plugintype, 'editor' => $editor, 'area'=>$filearea), '/mod/assign/portfolio_callback.php');
1679 $fs = get_file_storage();
1680
1681 if ($files = $fs->get_area_files($this->context->id, $component,$filearea, $submissionid, "timemodified", false)) {
1682 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
1683 } else {
1684 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
1685 }
1686 $result .= $button->to_html();
1687 }
1688 return $result;
1689 }
1690
bf78ebd6
DW
1691 /**
1692 * Display a grading error
1693 *
1694 * @param string $message - The description of the result
1695 * @return string
1696 */
1697 private function view_quickgrading_result($message) {
1698 $o = '';
1699 $o .= $this->output->render(new assign_header($this->get_instance(),
1700 $this->get_context(),
1701 $this->show_intro(),
1702 $this->get_course_module()->id,
1703 get_string('quickgradingresult', 'assign')));
1704 $o .= $this->output->render(new assign_quickgrading_result($message, $this->get_course_module()->id));
1705 $o .= $this->view_footer();
1706 return $o;
1707 }
bbd0e548
DW
1708
1709 /**
1710 * Display the page footer
1711 *
bf78ebd6 1712 * @return string
bbd0e548
DW
1713 */
1714 private function view_footer() {
1715 return $this->output->render_footer();
1716 }
1717
1718 /**
1719 * Does this user have grade permission for this assignment
1720 *
1721 * @return bool
1722 */
1723 private function can_grade() {
1724 // Permissions check
1725 if (!has_capability('mod/assign:grade', $this->context)) {
1726 return false;
1727 }
1728
1729 return true;
1730 }
1731
1732 /**
1733 * Download a zip file of all assignment submissions
1734 *
1735 * @return void
1736 */
1737 private function download_submissions() {
1738 global $CFG,$DB;
1739
1740 // more efficient to load this here
1741 require_once($CFG->libdir.'/filelib.php');
1742
1743 // load all submissions
1744 $submissions = $this->get_all_submissions('','');
1745
1746 if (empty($submissions)) {
1747 print_error('errornosubmissions', 'assign');
1748 return;
1749 }
1750
1751 // build a list of files to zip
1752 $filesforzipping = array();
1753 $fs = get_file_storage();
1754
1755 $groupmode = groups_get_activity_groupmode($this->get_course_module());
1756 $groupid = 0; // All users
1757 $groupname = '';
1758 if ($groupmode) {
1759 $groupid = groups_get_activity_group($this->get_course_module(), true);
1760 $groupname = groups_get_group_name($groupid).'-';
1761 }
1762
1763 // construct the zip file name
1764 $filename = str_replace(' ', '_', clean_filename($this->get_course()->shortname.'-'.$this->get_instance()->name.'-'.$groupname.$this->get_course_module()->id.".zip")); //name of new zip file.
1765
1766 // get all the files for each submission
1767 foreach ($submissions as $submission) {
1768 $userid = $submission->userid; //get userid
1769 if ((groups_is_member($groupid,$userid) or !$groupmode or !$groupid)) {
1770 // get the plugins to add their own files to the zip
1771
1772 $user = $DB->get_record("user", array("id"=>$userid),'id,username,firstname,lastname', MUST_EXIST);
1773
b473171a
DW
1774 if ($this->is_blind_marking()) {
1775 $prefix = clean_filename(get_string('participant', 'assign') . "_" . $this->get_uniqueid_for_user($userid) . "_");
1776 } else {
1777 $prefix = clean_filename(fullname($user) . "_" . $this->get_uniqueid_for_user($userid) . "_");
1778 }
bbd0e548
DW
1779
1780 foreach ($this->submissionplugins as $plugin) {
1781 if ($plugin->is_enabled() && $plugin->is_visible()) {
1782 $pluginfiles = $plugin->get_files($submission);
1783
1784
1785 foreach ($pluginfiles as $zipfilename => $file) {
1786 $filesforzipping[$prefix . $zipfilename] = $file;
1787 }
1788 }
1789 }
1790
1791 }
1792 } // end of foreach loop
1793 if ($zipfile = $this->pack_files($filesforzipping)) {
1794 $this->add_to_log('download all submissions', get_string('downloadall', 'assign'));
1795 send_temp_file($zipfile, $filename); //send file and delete after sending.
1796 }
1797 }
1798
1799 /**
1800 * Util function to add a message to the log
1801 *
1802 * @param string $action The current action
1803 * @param string $info A detailed description of the change. But no more than 255 characters.
1804 * @param string $url The url to the assign module instance.
1805 * @return void
1806 */
1807 public function add_to_log($action = '', $info = '', $url='') {
1808 global $USER;
1809
1810 $fullurl = 'view.php?id=' . $this->get_course_module()->id;
1811 if ($url != '') {
1812 $fullurl .= '&' . $url;
1813 }
1814
1815 add_to_log($this->get_course()->id, 'assign', $action, $fullurl, $info, $this->get_course_module()->id, $USER->id);
1816 }
1817
1818 /**
1819 * Load the submission object for a particular user, optionally creating it if required
1820 *
12a1a0da
DW
1821 * For team assignments there are 2 submissions - the student submission and the team submission
1822 * All files are associated with the team submission but the status of the students contribution is
1823 * recorded separately.
1824 *
bbd0e548
DW
1825 * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
1826 * @param bool $create optional Defaults to false. If set to true a new submission object will be created in the database
1827 * @return stdClass The submission
1828 */
1829 private function get_user_submission($userid, $create) {
1830 global $DB, $USER;
1831
1832 if (!$userid) {
1833 $userid = $USER->id;
1834 }
12a1a0da
DW
1835 // If the userid is not null then use userid.
1836 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
1837 $submission = $DB->get_record('assign_submission', $params);
bbd0e548
DW
1838
1839 if ($submission) {
1840 return $submission;
1841 }
1842 if ($create) {
1843 $submission = new stdClass();
1844 $submission->assignment = $this->get_instance()->id;
1845 $submission->userid = $userid;
1846 $submission->timecreated = time();
1847 $submission->timemodified = $submission->timecreated;
1848
1849 if ($this->get_instance()->submissiondrafts) {
1850 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1851 } else {
1852 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1853 }
1854 $sid = $DB->insert_record('assign_submission', $submission);
1855 $submission->id = $sid;
1856 return $submission;
1857 }
1858 return false;
1859 }
1860
1861 /**
1862 * Load the submission object from it's id
1863 *
1864 * @param int $submissionid The id of the submission we want
1865 * @return stdClass The submission
1866 */
1867 private function get_submission($submissionid) {
1868 global $DB;
1869
1870 return $DB->get_record('assign_submission', array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid), '*', MUST_EXIST);
1871 }
1872
1873 /**
1874 * This will retrieve a grade object from the db, optionally creating it if required
1875 *
1876 * @param int $userid The user we are grading
1877 * @param bool $create If true the grade will be created if it does not exist
1878 * @return stdClass The grade record
1879 */
1880 private function get_user_grade($userid, $create) {
1881 global $DB, $USER;
1882
1883 if (!$userid) {
1884 $userid = $USER->id;
1885 }
1886
1887 // if the userid is not null then use userid
1888 $grade = $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
1889
1890 if ($grade) {
1891 return $grade;
1892 }
1893 if ($create) {
1894 $grade = new stdClass();
1895 $grade->assignment = $this->get_instance()->id;
1896 $grade->userid = $userid;
1897 $grade->timecreated = time();
1898 $grade->timemodified = $grade->timecreated;
1899 $grade->locked = 0;
1900 $grade->grade = -1;
1901 $grade->grader = $USER->id;
1902 $gid = $DB->insert_record('assign_grades', $grade);
1903 $grade->id = $gid;
1904 return $grade;
1905 }
1906 return false;
1907 }
1908
1909 /**
1910 * This will retrieve a grade object from the db
1911 *
1912 * @param int $gradeid The id of the grade
1913 * @return stdClass The grade record
1914 */
1915 private function get_grade($gradeid) {
1916 global $DB;
1917
1918 return $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid), '*', MUST_EXIST);
1919 }
1920
1921 /**
1922 * Print the grading page for a single user submission
1923 *
1924 * @param moodleform $mform
1925 * @param int $offset
1926 * @return string
1927 */
1928 private function view_single_grade_page($mform, $offset=0) {
1929 global $DB, $CFG;
1930
1931 $o = '';
1932
1933 // Include grade form
1934 require_once($CFG->dirroot . '/mod/assign/gradeform.php');
1935
1936 // Need submit permission to submit an assignment
1937 require_capability('mod/assign:grade', $this->context);
1938
1939 $o .= $this->output->render(new assign_header($this->get_instance(),
1940 $this->get_context(), false, $this->get_course_module()->id,get_string('grading', 'assign')));
1941
1942 $rownum = required_param('rownum', PARAM_INT) + $offset;
1943 $useridlist = optional_param('useridlist', '', PARAM_TEXT);
1944 if ($useridlist) {
1945 $useridlist = explode(',', $useridlist);
1946 } else {
1947 $useridlist = $this->get_grading_userid_list();
1948 }
1949 $last = false;
1950 $userid = $useridlist[$rownum];
1951 if ($rownum == count($useridlist) - 1) {
1952 $last = true;
1953 }
1954 // the placement of this is important so can pass the list of userids above
1955 if ($offset) {
1956 $_POST = array();
1957 }
12a1a0da 1958 if (!$userid) {
bbd0e548
DW
1959 throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
1960 }
1961 $user = $DB->get_record('user', array('id' => $userid));
1962 if ($user) {
b473171a
DW
1963 $o .= $this->output->render(new assign_user_summary($user,
1964 $this->get_course()->id,
1965 has_capability('moodle/site:viewfullnames',
1966 $this->get_course_context()),
1967 $this->is_blind_marking(),
1968 $this->get_uniqueid_for_user($user->id)));
bbd0e548
DW
1969 }
1970 $submission = $this->get_user_submission($userid, false);
12a1a0da
DW
1971 $submissiongroup = null;
1972 $submissiongroupmemberswhohavenotsubmitted = array();
1973 $teamsubmission = null;
1974 $notsubmitted = array();
1975 if ($this->get_instance()->teamsubmission) {
1976 $teamsubmission = $this->get_group_submission($userid, 0, false);
1977 $submissiongroup = $this->get_submission_group($userid);
1978 $groupid = 0;
1979 if ($submissiongroup) {
1980 $groupid = $submissiongroup->id;
1981 }
1982 $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
1983
1984 }
1985
bbd0e548
DW
1986 // get the current grade
1987 $grade = $this->get_user_grade($userid, false);
1988 if ($this->can_view_submission($userid)) {
1989 $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($userid);
9e795179
DW
1990 $extensionduedate = null;
1991 if ($grade) {
1992 $extensionduedate = $grade->extensionduedate;
1993 }
88cfe469 1994 $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
9e795179 1995
12a1a0da
DW
1996 if ($teamsubmission) {
1997 $showsubmit = $showedit && $teamsubmission && ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
1998 } else {
1999 $showsubmit = $showedit && $submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
2000 }
2001 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
9e795179 2002
bbd0e548
DW
2003 $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
2004 $this->get_instance()->alwaysshowdescription,
2005 $submission,
12a1a0da
DW
2006 $this->get_instance()->teamsubmission,
2007 $teamsubmission,
2008 $submissiongroup,
2009 $notsubmitted,
bbd0e548
DW
2010 $this->is_any_submission_plugin_enabled(),
2011 $gradelocked,
2012 $this->is_graded($userid),
2013 $this->get_instance()->duedate,
9e795179 2014 $this->get_instance()->cutoffdate,
bbd0e548
DW
2015 $this->get_submission_plugins(),
2016 $this->get_return_action(),
2017 $this->get_return_params(),
2018 $this->get_course_module()->id,
12a1a0da 2019 $this->get_course()->id,
bbd0e548 2020 assign_submission_status::GRADER_VIEW,
9e795179
DW
2021 $showedit,
2022 $showsubmit,
12a1a0da
DW
2023 $viewfullnames,
2024 $extensionduedate,
88cfe469
DW
2025 $this->get_context(),
2026 $this->is_blind_marking()));
bbd0e548
DW
2027 }
2028 if ($grade) {
2029 $data = new stdClass();
2d8a9ce9 2030 if ($grade->grade !== NULL && $grade->grade >= 0) {
bbd0e548
DW
2031 $data->grade = format_float($grade->grade,2);
2032 }
2033 } else {
2034 $data = new stdClass();
2035 $data->grade = '';
2036 }
2037
2038 // now show the grading form
2039 if (!$mform) {
12a1a0da
DW
2040 $pagination = array( 'rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>$last);
2041 $formparams = array($this, $data, $pagination);
2042 $mform = new mod_assign_grade_form(null,
2043 $formparams,
2044 'post',
2045 '',
2046 array('class'=>'gradeform'));
bbd0e548
DW
2047 }
2048 $o .= $this->output->render(new assign_form('gradingform',$mform));
2049
12a1a0da
DW
2050 $msg = get_string('viewgradingformforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
2051 $this->add_to_log('view grading form', $msg);
bbd0e548
DW
2052
2053 $o .= $this->view_footer();
2054 return $o;
2055 }
2056
b473171a
DW
2057 /**
2058 * Show a confirmation page to make sure they want to release student identities
2059 *
2060 * @return string
2061 */
2062 private function view_reveal_identities_confirm() {
2063 global $CFG, $USER;
2064
2065 require_capability('mod/assign:revealidentities', $this->get_context());
2066
2067 $o = '';
2068 $o .= $this->output->render(new assign_header($this->get_instance(),
2069 $this->get_context(), false, $this->get_course_module()->id));
2070
2071 $confirmurl = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2072 'action'=>'revealidentitiesconfirm',
2073 'sesskey'=>sesskey()));
2074
2075 $cancelurl = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2076 'action'=>'grading'));
2077
2078 $o .= $this->output->confirm(get_string('revealidentitiesconfirm', 'assign'), $confirmurl, $cancelurl);
2079 $o .= $this->view_footer();
2080 $this->add_to_log('view', get_string('viewrevealidentitiesconfirm', 'assign'));
2081 return $o;
2082 }
2083
2084
bbd0e548
DW
2085
2086
2087 /**
2088 * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
2089 *
2090 * @return string
2091 */
2092 private function view_return_links() {
2093
2094 $returnaction = optional_param('returnaction','', PARAM_ALPHA);
2095 $returnparams = optional_param('returnparams','', PARAM_TEXT);
2096
2097 $params = array();
2098 parse_str($returnparams, $params);
2099 $params = array_merge( array('id' => $this->get_course_module()->id, 'action' => $returnaction), $params);
2100
2101 return $this->output->single_button(new moodle_url('/mod/assign/view.php', $params), get_string('back'), 'get');
2102
2103 }
2104
2105 /**
2106 * View the grading table of all submissions for this assignment
2107 *
2108 * @return string
2109 */
2110 private function view_grading_table() {
2111 global $USER, $CFG;
2112 // Include grading options form
2113 require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
bf78ebd6 2114 require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
bbd0e548
DW
2115 require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
2116 $o = '';
2117
2118 $links = array();
bbd0e548
DW
2119 if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
2120 has_capability('moodle/grade:viewall', $this->get_course_context())) {
a1e54f4d 2121 $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
bbd0e548
DW
2122 $links[$gradebookurl] = get_string('viewgradebook', 'assign');
2123 }
2124 if ($this->is_any_submission_plugin_enabled()) {
a1e54f4d 2125 $downloadurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=downloadall';
bbd0e548
DW
2126 $links[$downloadurl] = get_string('downloadall', 'assign');
2127 }
b473171a
DW
2128 if ($this->is_blind_marking() && has_capability('mod/assign:revealidentities', $this->get_context())) {
2129 $revealidentitiesurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=revealidentities';
2130 $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
2131 }
a1e54f4d
DW
2132
2133 $gradingactions = new url_select($links);
bbd0e548 2134
bf78ebd6
DW
2135 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
2136
bbd0e548
DW
2137 $perpage = get_user_preferences('assign_perpage', 10);
2138 $filter = get_user_preferences('assign_filter', '');
bf78ebd6
DW
2139 $controller = $gradingmanager->get_active_controller();
2140 $showquickgrading = empty($controller);
2141 if (optional_param('action', '', PARAM_ALPHA) == 'saveoptions') {
2142 $quickgrading = optional_param('quickgrading', false, PARAM_BOOL);
2143 set_user_preference('assign_quickgrading', $quickgrading);
2144 }
2145 $quickgrading = get_user_preferences('assign_quickgrading', false);
2146
bbd0e548
DW
2147 // print options for changing the filter and changing the number of results per page
2148 $gradingoptionsform = new mod_assign_grading_options_form(null,
2149 array('cm'=>$this->get_course_module()->id,
2150 'contextid'=>$this->context->id,
bf78ebd6 2151 'userid'=>$USER->id,
44e2f0fe 2152 'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
bf78ebd6
DW
2153 'showquickgrading'=>$showquickgrading,
2154 'quickgrading'=>$quickgrading),
bbd0e548
DW
2155 'post', '',
2156 array('class'=>'gradingoptionsform'));
2157
2158 $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
2159 array('cm'=>$this->get_course_module()->id,
9e795179
DW
2160 'submissiondrafts'=>$this->get_instance()->submissiondrafts,
2161 'duedate'=>$this->get_instance()->duedate),
bbd0e548
DW
2162 'post', '',
2163 array('class'=>'gradingbatchoperationsform'));
2164
2165 $gradingoptionsdata = new stdClass();
2166 $gradingoptionsdata->perpage = $perpage;
2167 $gradingoptionsdata->filter = $filter;
2168 $gradingoptionsform->set_data($gradingoptionsdata);
2169
2170 // plagiarism update status apearring in the grading book
2171 if (!empty($CFG->enableplagiarism)) {
2172 /** Include plagiarismlib.php */
2173 require_once($CFG->libdir . '/plagiarismlib.php');
9650334f 2174 $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
bbd0e548
DW
2175 }
2176
a1e54f4d
DW
2177 $actionformtext = $this->output->render($gradingactions);
2178 $o .= $this->output->render(new assign_header($this->get_instance(),
2179 $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign'), $actionformtext));
2180 $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
2181
bbd0e548
DW
2182
2183 // load and print the table of submissions
bf78ebd6
DW
2184 if ($showquickgrading && $quickgrading) {
2185 $table = $this->output->render(new assign_grading_table($this, $perpage, $filter, 0, true));
2186 $quickgradingform = new mod_assign_quick_grading_form(null,
2187 array('cm'=>$this->get_course_module()->id,
2188 'gradingtable'=>$table));
2189 $o .= $this->output->render(new assign_form('quickgradingform', $quickgradingform));
2190 } else {
2191 $o .= $this->output->render(new assign_grading_table($this, $perpage, $filter, 0, false));
2192 }
bbd0e548
DW
2193
2194 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
2195 $users = array_keys($this->list_participants($currentgroup, true));
2196 if (count($users) != 0) {
2197 // if no enrolled user in a course then don't display the batch operations feature
2198 $o .= $this->output->render(new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform));
2199 }
a1e54f4d 2200 $o .= $this->output->render(new assign_form('gradingoptionsform', $gradingoptionsform, 'M.mod_assign.init_grading_options'));
bbd0e548
DW
2201 return $o;
2202 }
2203
2204 /**
2205 * View entire grading page.
2206 *
2207 * @return string
2208 */
2209 private function view_grading_page() {
2210 global $CFG;
2211
2212 $o = '';
2213 // Need submit permission to submit an assignment
2214 require_capability('mod/assign:grade', $this->context);
2215 require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2216
2217 // only load this if it is
2218
bbd0e548
DW
2219 $o .= $this->view_grading_table();
2220
2221 $o .= $this->view_footer();
2222 $this->add_to_log('view submission grading table', get_string('viewsubmissiongradingtable', 'assign'));
2223 return $o;
2224 }
2225
2226 /**
2227 * Capture the output of the plagiarism plugins disclosures and return it as a string
2228 *
2229 * @return void
2230 */
2231 private function plagiarism_print_disclosure() {
2232 global $CFG;
2233 $o = '';
2234
2235 if (!empty($CFG->enableplagiarism)) {
2236 /** Include plagiarismlib.php */
2237 require_once($CFG->libdir . '/plagiarismlib.php');
bbd0e548 2238
9650334f 2239 $o .= plagiarism_print_disclosure($this->get_course_module()->id);
bbd0e548
DW
2240 }
2241
2242 return $o;
2243 }
2244
2245 /**
2246 * message for students when assignment submissions have been closed
2247 *
2248 * @return string
2249 */
2250 private function view_student_error_message() {
2251 global $CFG;
2252
2253 $o = '';
2254 // Need submit permission to submit an assignment
2255 require_capability('mod/assign:submit', $this->context);
2256
2257 $o .= $this->output->render(new assign_header($this->get_instance(),
2258 $this->get_context(),
2259 $this->show_intro(),
2260 $this->get_course_module()->id,
2261 get_string('editsubmission', 'assign')));
2262
bf78ebd6 2263 $o .= $this->output->notification(get_string('submissionsclosed', 'assign'));
bbd0e548
DW
2264
2265 $o .= $this->view_footer();
2266
2267 return $o;
2268
2269 }
2270
2271 /**
2272 * View edit submissions page.
2273 *
2274 * @param moodleform $mform
2275 * @return void
2276 */
2277 private function view_edit_submission_page($mform) {
2278 global $CFG;
2279
2280 $o = '';
2281 // Include submission form
2282 require_once($CFG->dirroot . '/mod/assign/submission_form.php');
2283 // Need submit permission to submit an assignment
2284 require_capability('mod/assign:submit', $this->context);
2285
2286 if (!$this->submissions_open()) {
bf78ebd6 2287 return $this->view_student_error_message();
bbd0e548
DW
2288 }
2289 $o .= $this->output->render(new assign_header($this->get_instance(),
2290 $this->get_context(),
2291 $this->show_intro(),
2292 $this->get_course_module()->id,
2293 get_string('editsubmission', 'assign')));
2294 $o .= $this->plagiarism_print_disclosure();
2295 $data = new stdClass();
2296
2297 if (!$mform) {
2298 $mform = new mod_assign_submission_form(null, array($this, $data));
2299 }
2300
2301 $o .= $this->output->render(new assign_form('editsubmissionform',$mform));
2302
2303 $o .= $this->view_footer();
2304 $this->add_to_log('view submit assignment form', get_string('viewownsubmissionform', 'assign'));
2305
2306 return $o;
2307 }
2308
2309 /**
2310 * See if this assignment has a grade yet
2311 *
2312 * @param int $userid
2313 * @return bool
2314 */
2315 private function is_graded($userid) {
2316 $grade = $this->get_user_grade($userid, false);
2317 if ($grade) {
2d8a9ce9 2318 return ($grade->grade !== NULL && $grade->grade >= 0);
bbd0e548
DW
2319 }
2320 return false;
2321 }
2322
2323
2324 /**
2325 * Perform an access check to see if the current $USER can view this users submission
2326 *
2327 * @param int $userid
2328 * @return bool
2329 */
2330 public function can_view_submission($userid) {
2331 global $USER;
2332
2333 if (!is_enrolled($this->get_course_context(), $userid)) {
2334 return false;
2335 }
2336 if ($userid == $USER->id && !has_capability('mod/assign:submit', $this->context)) {
2337 return false;
2338 }
2339 if ($userid != $USER->id && !has_capability('mod/assign:grade', $this->context)) {
2340 return false;
2341 }
2342 return true;
2343 }
2344
2345 /**
2346 * Ask the user to confirm they want to perform this batch operation
9e795179 2347 * @return string - the page to view after processing these actions
bbd0e548
DW
2348 */
2349 private function process_batch_grading_operation() {
2350 global $CFG;
2351 require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
2352 require_sesskey();
2353
2354 $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
2355 array('cm'=>$this->get_course_module()->id,
9e795179
DW
2356 'submissiondrafts'=>$this->get_instance()->submissiondrafts,
2357 'duedate'=>$this->get_instance()->duedate),
bbd0e548
DW
2358 'post', '',
2359 array('class'=>'gradingbatchoperationsform'));
2360
2361 if ($data = $gradingbatchoperationsform->get_data()) {
2362 // get the list of users
2363 $users = $data->selectedusers;
2364 $userlist = explode(',', $users);
2365
2366 foreach ($userlist as $userid) {
2367 if ($data->operation == 'lock') {
2368 $this->process_lock($userid);
2369 } else if ($data->operation == 'unlock') {
2370 $this->process_unlock($userid);
2371 } else if ($data->operation == 'reverttodraft') {
2372 $this->process_revert_to_draft($userid);
9e795179
DW
2373 } else if ($data->operation == 'grantextension') {
2374 return 'grantextension';
bbd0e548
DW
2375 }
2376 }
2377 }
2378
9e795179 2379 return 'grading';
bbd0e548
DW
2380 }
2381
2382 /**
2383 * Ask the user to confirm they want to submit their work for grading
94f26900 2384 * @param $mform moodleform - null unless form validation has failed
bbd0e548
DW
2385 * @return string
2386 */
94f26900
DW
2387 private function check_submit_for_grading($mform) {
2388 global $USER, $CFG;
2389
2390 require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
2391
bbd0e548
DW
2392 // Check that all of the submission plugins are ready for this submission
2393 $notifications = array();
2394 $submission = $this->get_user_submission($USER->id, false);
2395 $plugins = $this->get_submission_plugins();
2396 foreach ($plugins as $plugin) {
2397 if ($plugin->is_enabled() && $plugin->is_visible()) {
2398 $check = $plugin->precheck_submission($submission);
2399 if ($check !== true) {
2400 $notifications[] = $check;
2401 }
2402 }
2403 }
2404
94f26900
DW
2405 $data = new stdClass();
2406 $adminconfig = $this->get_admin_config();
2407 $requiresubmissionstatement = !empty($adminconfig->requiresubmissionstatement) ||
2408 $this->get_instance()->requiresubmissionstatement;
2409
2410 $submissionstatement = '';
2411 if (!empty($adminconfig->submissionstatement)) {
2412 $submissionstatement = $adminconfig->submissionstatement;
2413 }
2414
2415 if ($mform == null) {
2416 $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
2417 $submissionstatement,
2418 $this->get_course_module()->id,
2419 $data));
2420 }
bbd0e548
DW
2421 $o = '';
2422 $o .= $this->output->header();
94f26900 2423 $o .= $this->output->render(new assign_submit_for_grading_page($notifications, $this->get_course_module()->id, $mform));
bbd0e548 2424 $o .= $this->view_footer();
94f26900
DW
2425
2426 $this->add_to_log('view confirm submit assignment form', get_string('viewownsubmissionform', 'assign'));
2427
bbd0e548
DW
2428 return $o;
2429 }
2430
2431 /**
2432 * Print 2 tables of information with no action links -
2433 * the submission summary and the grading summary
2434 *
2435 * @param stdClass $user the user to print the report for
2436 * @param bool $showlinks - Return plain text or links to the profile
2437 * @return string - the html summary
2438 */
2439 public function view_student_summary($user, $showlinks) {
2440 global $CFG, $DB, $PAGE;
2441
2442 $grade = $this->get_user_grade($user->id, false);
2443 $submission = $this->get_user_submission($user->id, false);
2444 $o = '';
2445
12a1a0da
DW
2446 $teamsubmission = null;
2447 $submissiongroup = null;
2448 $notsubmitted = array();
2449 if ($this->get_instance()->teamsubmission) {
2450 $teamsubmission = $this->get_group_submission($user->id, 0, false);
2451 $submissiongroup = $this->get_submission_group($user->id);
2452 $groupid = 0;
2453 if ($submissiongroup) {
2454 $groupid = $submissiongroup->id;
2455 }
2456 $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
2457 }
2458
bbd0e548
DW
2459 if ($this->can_view_submission($user->id)) {
2460 $showedit = has_capability('mod/assign:submit', $this->context) &&
9e795179 2461 $this->submissions_open($user->id) && ($this->is_any_submission_plugin_enabled()) && $showlinks;
bbd0e548 2462 $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($user->id);
12a1a0da
DW
2463
2464 $showsubmit = ($submission || $teamsubmission) && $showlinks;
2465 if ($teamsubmission && ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
2466 $showsubmit = false;
2467 }
2468 if ($submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
2469 $showsubmit = false;
2470 }
9e795179
DW
2471 $extensionduedate = null;
2472 if ($grade) {
2473 $extensionduedate = $grade->extensionduedate;
2474 }
12a1a0da 2475 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
bbd0e548
DW
2476 $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
2477 $this->get_instance()->alwaysshowdescription,
2478 $submission,
12a1a0da
DW
2479 $this->get_instance()->teamsubmission,
2480 $teamsubmission,
2481 $submissiongroup,
2482 $notsubmitted,
bbd0e548
DW
2483 $this->is_any_submission_plugin_enabled(),
2484 $gradelocked,
2485 $this->is_graded($user->id),
2486 $this->get_instance()->duedate,
9e795179 2487 $this->get_instance()->cutoffdate,
bbd0e548
DW
2488 $this->get_submission_plugins(),
2489 $this->get_return_action(),
2490 $this->get_return_params(),
2491 $this->get_course_module()->id,
12a1a0da 2492 $this->get_course()->id,
bbd0e548
DW
2493 assign_submission_status::STUDENT_VIEW,
2494 $showedit,
9e795179 2495 $showsubmit,
12a1a0da
DW
2496 $viewfullnames,
2497 $extensionduedate,
b98824c2
DW
2498 $this->get_context(),
2499 $this->is_blind_marking()));
bbd0e548
DW
2500 require_once($CFG->libdir.'/gradelib.php');
2501 require_once($CFG->dirroot.'/grade/grading/lib.php');
2502
2503 $gradinginfo = grade_get_grades($this->get_course()->id,
2504 'mod',
2505 'assign',
2506 $this->get_instance()->id,
2507 $user->id);
2508
2509 $gradingitem = $gradinginfo->items[0];
2510 $gradebookgrade = $gradingitem->grades[$user->id];
2511
2512 // check to see if all feedback plugins are empty
2513 $emptyplugins = true;
2514 if ($grade) {
2515 foreach ($this->get_feedback_plugins() as $plugin) {
2516 if ($plugin->is_visible() && $plugin->is_enabled()) {
2517 if (!$plugin->is_empty($grade)) {
2518 $emptyplugins = false;
2519 }
2520 }
2521 }
2522 }
2523
2524
2525 if (!($gradebookgrade->hidden) && ($gradebookgrade->grade !== null || !$emptyplugins)) {
2526
2527 $gradefordisplay = '';
2528 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
2529
2530 if ($controller = $gradingmanager->get_active_controller()) {
2531 $controller->set_grade_range(make_grades_menu($this->get_instance()->grade));
2532 $gradefordisplay = $controller->render_grade($PAGE,
2533 $grade->id,
2534 $gradingitem,
2535 $gradebookgrade->str_long_grade,
2536 has_capability('mod/assign:grade', $this->get_context()));
2537 } else {
bf78ebd6 2538 $gradefordisplay = $this->display_grade($gradebookgrade->grade, false);
bbd0e548
DW
2539 }
2540
2541 $gradeddate = $gradebookgrade->dategraded;
2542 $grader = $DB->get_record('user', array('id'=>$gradebookgrade->usermodified));
2543
2544 $feedbackstatus = new assign_feedback_status($gradefordisplay,
2545 $gradeddate,
2546 $grader,
2547 $this->get_feedback_plugins(),
2548 $grade,
2549 $this->get_course_module()->id,
2550 $this->get_return_action(),
2551 $this->get_return_params());
2552
2553 $o .= $this->output->render($feedbackstatus);
2554 }
2555
2556 }
2557 return $o;
2558 }
2559
2560 /**
2561 * View submissions page (contains details of current submission).
2562 *
2563 * @return string
2564 */
2565 private function view_submission_page() {
2566 global $CFG, $DB, $USER, $PAGE;
2567
2568 $o = '';
2569 $o .= $this->output->render(new assign_header($this->get_instance(),
2570 $this->get_context(),
2571 $this->show_intro(),
2572 $this->get_course_module()->id));
2573
2574 if ($this->can_grade()) {
12a1a0da
DW
2575 if ($this->get_instance()->teamsubmission) {
2576 $summary = new assign_grading_summary($this->count_teams(),
2577 $this->get_instance()->submissiondrafts,
2578 $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
2579 $this->is_any_submission_plugin_enabled(),
2580 $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
2581 $this->get_instance()->cutoffdate,
2582 $this->get_instance()->duedate,
2583 $this->get_course_module()->id,
2584 $this->count_submissions_need_grading(),
2585 $this->get_instance()->teamsubmission);
2586 $o .= $this->output->render($summary);
2587 } else {
2588 $summary = new assign_grading_summary($this->count_participants(0),
2589 $this->get_instance()->submissiondrafts,
2590 $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
2591 $this->is_any_submission_plugin_enabled(),
2592 $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
2593 $this->get_instance()->cutoffdate,
2594 $this->get_instance()->duedate,
2595 $this->get_course_module()->id,
2596 $this->count_submissions_need_grading(),
2597 $this->get_instance()->teamsubmission);
2598 $o .= $this->output->render($summary);
2599 }
bbd0e548
DW
2600 }
2601 $grade = $this->get_user_grade($USER->id, false);
2602 $submission = $this->get_user_submission($USER->id, false);
2603
2604 if ($this->can_view_submission($USER->id)) {
2605 $o .= $this->view_student_summary($USER, true);
2606 }
2607
2608
2609 $o .= $this->view_footer();
2610 $this->add_to_log('view', get_string('viewownsubmissionstatus', 'assign'));
2611 return $o;
2612 }
2613
2614 /**
2615 * convert the final raw grade(s) in the grading table for the gradebook
2616 *
2617 * @param stdClass $grade
2618 * @return array
2619 */
2620 private function convert_grade_for_gradebook(stdClass $grade) {
2621 $gradebookgrade = array();
2622 // trying to match those array keys in grade update function in gradelib.php
2623 // with keys in th database table assign_grades
2624 // starting around line 262
99467a9f
DW
2625 if ($grade->grade >= 0) {
2626 $gradebookgrade['rawgrade'] = $grade->grade;
2627 }
bbd0e548
DW
2628 $gradebookgrade['userid'] = $grade->userid;
2629 $gradebookgrade['usermodified'] = $grade->grader;
2630 $gradebookgrade['datesubmitted'] = NULL;
2631 $gradebookgrade['dategraded'] = $grade->timemodified;
2632 if (isset($grade->feedbackformat)) {
2633 $gradebookgrade['feedbackformat'] = $grade->feedbackformat;
2634 }
2635 if (isset($grade->feedbacktext)) {
2636 $gradebookgrade['feedback'] = $grade->feedbacktext;
2637 }
2638
2639 return $gradebookgrade;
2640 }
2641
2642 /**
2643 * convert submission details for the gradebook
2644 *
2645 * @param stdClass $submission
2646 * @return array
2647 */
2648 private function convert_submission_for_gradebook(stdClass $submission) {
2649 $gradebookgrade = array();
2650
2651
2652 $gradebookgrade['userid'] = $submission->userid;
2653 $gradebookgrade['usermodified'] = $submission->userid;
2654 $gradebookgrade['datesubmitted'] = $submission->timemodified;
2655
2656 return $gradebookgrade;
2657 }
2658
2659 /**
2660 * update grades in the gradebook
2661 *
2662 * @param mixed $submission stdClass|null
2663 * @param mixed $grade stdClass|null
2664 * @return bool
2665 */
2666 private function gradebook_item_update($submission=NULL, $grade=NULL) {
2667
b473171a
DW
2668 // Do not push grade to gradebook if blind marking is active as the gradebook would reveal the students.
2669 if ($this->is_blind_marking()) {
2670 return false;
2671 }
12a1a0da
DW
2672 if ($submission != NULL) {
2673 if ($submission->userid == 0) {
2674 // This is a group submission update.
2675 $team = groups_get_members($submission->groupid, 'u.id');
2676
2677 foreach ($team as $member) {
2678 $submission->groupid = 0;
2679 $submission->userid = $member->id;
2680 $this->gradebook_item_update($submission, null);
2681 }
2682 return;
2683 }
2684
bbd0e548 2685 $gradebookgrade = $this->convert_submission_for_gradebook($submission);
12a1a0da
DW
2686
2687 } else {
bbd0e548
DW
2688 $gradebookgrade = $this->convert_grade_for_gradebook($grade);
2689 }
09b46f0e
AA
2690 // Grading is disabled, return.
2691 if ($this->grading_disabled($gradebookgrade['userid'])) {
2692 return false;
2693 }
bbd0e548
DW
2694 $assign = clone $this->get_instance();
2695 $assign->cmidnumber = $this->get_course_module()->id;
2696
2697 return assign_grade_item_update($assign, $gradebookgrade);
2698 }
2699
12a1a0da
DW
2700 /**
2701 * update team submission
2702 *
2703 * @param stdClass $submission
2704 * @param int $userid
2705 * @param bool $updatetime
2706 * @return bool
2707 */
2708 private function update_team_submission(stdClass $submission, $userid, $updatetime) {
2709 global $DB;
2710
2711 if ($updatetime) {
2712 $submission->timemodified = time();
2713 }
2714
2715 // First update the submission for the current user.
2716 $mysubmission = $this->get_user_submission($userid, true);
2717 $mysubmission->status = $submission->status;
2718
2719 $this->update_submission($mysubmission, 0, $updatetime, false);
2720
2721 // Now check the team settings to see if this assignment qualifies as submitted or draft.
2722 $team = $this->get_submission_group_members($submission->groupid, true);
2723
2724 $allsubmitted = true;
2725 $anysubmitted = false;
2726 foreach ($team as $member) {
2727 $membersubmission = $this->get_user_submission($member->id, false);
2728
2729 if (!$membersubmission || $membersubmission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2730 $allsubmitted = false;
2731 if ($anysubmitted) {
2732 break;
2733 }
2734 } else {
2735 $anysubmitted = true;
2736 }
2737 }
2738 if ($this->get_instance()->requireallteammemberssubmit) {
2739 if ($allsubmitted) {
2740 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2741 } else {
2742 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
2743 }
2744 $result= $DB->update_record('assign_submission', $submission);
2745 } else {
2746 if ($anysubmitted) {
2747 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2748 } else {
2749 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
2750 }
2751 $result= $DB->update_record('assign_submission', $submission);
2752 }
2753
2754 $this->gradebook_item_update($submission);
2755 return $result;
2756 }
2757
2758
bbd0e548
DW
2759 /**
2760 * update grades in the gradebook based on submission time
2761 *
2762 * @param stdClass $submission
12a1a0da 2763 * @param int $userid
bbd0e548 2764 * @param bool $updatetime
12a1a0da 2765 * @param bool $teamsubmission
bbd0e548
DW
2766 * @return bool
2767 */
12a1a0da 2768 private function update_submission(stdClass $submission, $userid, $updatetime, $teamsubmission) {
bbd0e548
DW
2769 global $DB;
2770
12a1a0da
DW
2771 if ($teamsubmission) {
2772 return $this->update_team_submission($submission, $userid, $updatetime);
2773 }
2774
bbd0e548
DW
2775 if ($updatetime) {
2776 $submission->timemodified = time();
2777 }
2778 $result= $DB->update_record('assign_submission', $submission);
2779 if ($result) {
2780 $this->gradebook_item_update($submission);
2781 }
2782 return $result;
2783 }
2784
2785 /**
2786 * Is this assignment open for submissions?
2787 *
2788 * Check the due date,
2789 * prevent late submissions,
2790 * has this person already submitted,
2791 * is the assignment locked?
2792 *
9e795179 2793 * @param int $userid - Optional userid so we can see if a different user can submit
bbd0e548
DW
2794 * @return bool
2795 */
9e795179 2796 private function submissions_open($userid = 0) {
bbd0e548
DW
2797 global $USER;
2798
9e795179
DW
2799 if (!$userid) {
2800 $userid = $USER->id;
2801 }
2802
bbd0e548
DW
2803 $time = time();
2804 $dateopen = true;
9e795179
DW
2805 $finaldate = false;
2806 if ($this->get_instance()->cutoffdate) {
2807 $finaldate = $this->get_instance()->cutoffdate;
2808 }
2809 // User extensions.
2810 if ($finaldate) {
2811 $grade = $this->get_user_grade($userid, false);
2812 if ($grade && $grade->extensionduedate) {
2813 // Extension can be before cut off date.
2814 if ($grade->extensionduedate > $finaldate) {
2815 $finaldate = $grade->extensionduedate;
2816 }
2817 }
2818 }
2819
2820 if ($finaldate) {
2821 $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $finaldate);
bbd0e548
DW
2822 } else {
2823 $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time);
2824 }
2825
2826 if (!$dateopen) {
2827 return false;
2828 }
2829
9e795179
DW
2830 // Now check if this user has already submitted etc.
2831 if (!is_enrolled($this->get_course_context(), $userid)) {
bbd0e548
DW
2832 return false;
2833 }
cd01491c
DW
2834 $submission = false;
2835 if ($this->get_instance()->teamsubmission) {
2836 $submission = $this->get_group_submission($USER->id, 0, false);
2837 } else {
2838 $submission = $this->get_user_submission($USER->id, false);
2839 }
2840 if ($submission) {
2841
bbd0e548
DW
2842 if ($this->get_instance()->submissiondrafts && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2843 // drafts are tracked and the student has submitted the assignment
2844 return false;
2845 }
2846 }
9e795179 2847 if ($grade = $this->get_user_grade($userid, false)) {
bbd0e548
DW
2848 if ($grade->locked) {
2849 return false;
2850 }
2851 }
2852
9e795179 2853 if ($this->grading_disabled($userid)) {
bbd0e548
DW
2854 return false;
2855 }
2856
2857 return true;
2858 }
2859
2860 /**
2861 * render the files in file area
2862 * @param string $component
2863 * @param string $area
2864 * @param int $submissionid
2865 * @return string
2866 */
2867 public function render_area_files($component, $area, $submissionid) {
2868 global $USER;
2869
bbd0e548
DW
2870 $fs = get_file_storage();
2871 $browser = get_file_browser();
2872 $files = $fs->get_area_files($this->get_context()->id, $component, $area , $submissionid , "timemodified", false);
2873 return $this->output->assign_files($this->context, $submissionid, $area, $component);
2874
2875 }
2876
2877 /**
2878 * Returns a list of teachers that should be grading given submission
2879 *
75f87a57 2880 * @param int $userid
bbd0e548
DW
2881 * @return array
2882 */
75f87a57 2883 private function get_graders($userid) {
bbd0e548 2884 //potential graders
75f87a57 2885 $potentialgraders = get_enrolled_users($this->context, "mod/assign:grade");
bbd0e548
DW
2886
2887 $graders = array();
2888 if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) { // Separate groups are being used
75f87a57 2889 if ($groups = groups_get_all_groups($this->get_course()->id, $userid)) { // Try to find all groups
bbd0e548
DW
2890 foreach ($groups as $group) {
2891 foreach ($potentialgraders as $grader) {
75f87a57 2892 if ($grader->id == $userid) {
bbd0e548
DW
2893 continue; // do not send self
2894 }
2895 if (groups_is_member($group->id, $grader->id)) {
2896 $graders[$grader->id] = $grader;
2897 }
2898 }
2899 }
2900 } else {
2901 // user not in group, try to find graders without group
2902 foreach ($potentialgraders as $grader) {
75f87a57 2903 if ($grader->id == $userid) {
bbd0e548
DW
2904 continue; // do not send self
2905 }
2906 if (!groups_has_membership($this->get_course_module(), $grader->id)) {
2907 $graders[$grader->id] = $grader;
2908 }
2909 }
2910 }
2911 } else {
2912 foreach ($potentialgraders as $grader) {
75f87a57 2913 if ($grader->id == $userid) {
bbd0e548
DW
2914 continue; // do not send self
2915 }
2916 // must be enrolled
2917 if (is_enrolled($this->get_course_context(), $grader->id)) {
2918 $graders[$grader->id] = $grader;
2919 }
2920 }
2921 }
2922 return $graders;
2923 }
2924
2925 /**
3f7b501e 2926 * Format a notification for plain text
bbd0e548 2927 *
75f87a57
DW
2928 * @param string $messagetype
2929 * @param stdClass $info
2930 * @param stdClass $course
2931 * @param stdClass $context
2932 * @param string $modulename
2933 * @param string $assignmentname
bbd0e548 2934 */
75f87a57
DW
2935 private static function format_notification_message_text($messagetype, $info, $course, $context, $modulename, $assignmentname) {
2936 $posttext = format_string($course->shortname, true, array('context' => $context->get_course_context())).' -> '.
2937 $modulename.' -> '.
2938 format_string($assignmentname, true, array('context' => $context))."\n";
bbd0e548 2939 $posttext .= '---------------------------------------------------------------------'."\n";
75f87a57 2940 $posttext .= get_string($messagetype . 'text', "assign", $info)."\n";
bbd0e548
DW
2941 $posttext .= "\n---------------------------------------------------------------------\n";
2942 return $posttext;
2943 }
2944
75f87a57 2945 /**
3f7b501e 2946 * Format a notification for HTML
bbd0e548 2947 *
75f87a57
DW
2948 * @param string $messagetype
2949 * @param stdClass $info
3f7b501e
SH
2950 * @param stdClass $course
2951 * @param stdClass $context
2952 * @param string $modulename
2953 * @param stdClass $coursemodule
2954 * @param string $assignmentname
2955 * @param stdClass $info
bbd0e548 2956 */
75f87a57 2957 private static function format_notification_message_html($messagetype, $info, $course, $context, $modulename, $coursemodule, $assignmentname) {
bbd0e548
DW
2958 global $CFG;
2959 $posthtml = '<p><font face="sans-serif">'.
75f87a57 2960 '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.format_string($course->shortname, true, array('context' => $context->get_course_context())).'</a> ->'.
599f7a27
RK
2961 '<a href="'.$CFG->wwwroot.'/mod/assign/index.php?id='.$course->id.'">'.$modulename.'</a> ->'.
2962 '<a href="'.$CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id.'">'.format_string($assignmentname, true, array('context' => $context)).'</a></font></p>';
bbd0e548 2963 $posthtml .= '<hr /><font face="sans-serif">';
75f87a57 2964 $posthtml .= '<p>'.get_string($messagetype . 'html', 'assign', $info).'</p>';
bbd0e548
DW
2965 $posthtml .= '</font><hr />';
2966 return $posthtml;
2967 }
2968
75f87a57 2969 /**
c1d09c6f 2970 * Message someone about something (static so it can be called from cron)
75f87a57 2971 *
75f87a57
DW
2972 * @param stdClass $userfrom
2973 * @param stdClass $userto
2974 * @param string $messagetype
2975 * @param string $eventtype
2976 * @param int $updatetime
2977 * @param stdClass $coursemodule
2978 * @param stdClass $context
2979 * @param stdClass $course
2980 * @param string $modulename
75f87a57
DW
2981 * @param string $assignmentname
2982 * @return void
2983 */
2984 public static function send_assignment_notification($userfrom, $userto, $messagetype, $eventtype,
2985 $updatetime, $coursemodule, $context, $course,
b473171a
DW
2986 $modulename, $assignmentname, $blindmarking,
2987 $uniqueidforuser) {
3f7b501e 2988 global $CFG;
75f87a57
DW
2989
2990 $info = new stdClass();
b473171a
DW
2991 if ($blindmarking) {
2992 $info->username = get_string('participant', 'assign') . ' ' . $uniqueidforuser;
2993 } else {
2994 $info->username = fullname($userfrom, true);
2995 }
75f87a57
DW
2996 $info->assignment = format_string($assignmentname,true, array('context'=>$context));
2997 $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id;
2998 $info->timeupdated = strftime('%c',$updatetime);
2999
3000 $postsubject = get_string($messagetype . 'small', 'assign', $info);
3001 $posttext = self::format_notification_message_text($messagetype, $info, $course, $context, $modulename, $assignmentname);
3002 $posthtml = ($userto->mailformat == 1) ? self::format_notification_message_html($messagetype, $info, $course, $context, $modulename, $coursemodule, $assignmentname) : '';
3003
3004 $eventdata = new stdClass();
3005 $eventdata->modulename = 'assign';
3006 $eventdata->userfrom = $userfrom;
3007 $eventdata->userto = $userto;
3008 $eventdata->subject = $postsubject;
3009 $eventdata->fullmessage = $posttext;
3010 $eventdata->fullmessageformat = FORMAT_PLAIN;
3011 $eventdata->fullmessagehtml = $posthtml;
3012 $eventdata->smallmessage = $postsubject;
3013
3014 $eventdata->name = $eventtype;
3015 $eventdata->component = 'mod_assign';
3016 $eventdata->notification = 1;
3017 $eventdata->contexturl = $info->url;
3018 $eventdata->contexturlname = $info->assignment;
3019
3020 message_send($eventdata);
75f87a57
DW
3021 }
3022
3023 /**
c1d09c6f 3024 * Message someone about something
75f87a57 3025 *
3f7b501e
SH
3026 * @param stdClass $userfrom
3027 * @param stdClass $userto
3028 * @param string $messagetype
3029 * @param string $eventtype
3030 * @param int $updatetime
75f87a57
DW
3031 * @return void
3032 */
3033 public function send_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime) {
b473171a
DW
3034 self::send_assignment_notification($userfrom, $userto, $messagetype, $eventtype,
3035 $updatetime, $this->get_course_module(), $this->get_context(),
3036 $this->get_course(), $this->get_module_name(),
3037 $this->get_instance()->name, $this->is_blind_marking(),
3038 $this->get_uniqueid_for_user($userfrom->id));
75f87a57
DW
3039 }
3040
3041 /**
c1d09c6f 3042 * Notify student upon successful submission
75f87a57 3043 *
75f87a57
DW
3044 * @param stdClass $submission
3045 * @return void
3046 */
c1d09c6f 3047 private function notify_student_submission_receipt(stdClass $submission) {
12a1a0da 3048 global $DB, $USER;
75f87a57
DW
3049
3050 $adminconfig = $this->get_admin_config();
94f26900 3051 if (empty($adminconfig->submissionreceipts)) {
3f7b501e 3052 // No need to do anything
75f87a57
DW
3053 return;
3054 }
12a1a0da
DW
3055 if ($submission->userid) {
3056 $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
3057 } else {
3058 $user = $USER;
3059 }
f750cf71 3060 $this->send_notification($user, $user, 'submissionreceipt', 'assign_notification', $submission->timemodified);
75f87a57
DW
3061 }
3062
bbd0e548 3063 /**
c1d09c6f 3064 * Send notifications to graders upon student submissions
bbd0e548
DW
3065 *
3066 * @param stdClass $submission
3067 * @return void
3068 */
c1d09c6f 3069 private function notify_graders(stdClass $submission) {
12a1a0da 3070 global $DB, $USER;
75f87a57
DW
3071
3072 $late = $this->get_instance()->duedate && ($this->get_instance()->duedate < time());
bbd0e548 3073
75f87a57 3074 if (!$this->get_instance()->sendnotifications && !($late && $this->get_instance()->sendlatenotifications)) { // No need to do anything
bbd0e548
DW
3075 return;
3076 }
3077
12a1a0da
DW
3078 if ($submission->userid) {
3079 $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
3080 } else {
3081 $user = $USER;
3082 }
75f87a57 3083 if ($teachers = $this->get_graders($user->id)) {
bbd0e548 3084 foreach ($teachers as $teacher) {
f750cf71 3085 $this->send_notification($user, $teacher, 'gradersubmissionupdated', 'assign_notification', $submission->timemodified);
bbd0e548
DW
3086 }
3087 }
3088 }
3089
3090 /**
3091 * assignment submission is processed before grading
3092 *
94f26900
DW
3093 * @param $mform If validation failed when submitting this form - this is the moodleform - it can be null
3094 * @return bool Return false if the validation fails. This affects which page is displayed next.
bbd0e548 3095 */
94f26900
DW
3096 private function process_submit_for_grading($mform) {
3097 global $USER, $CFG;
bbd0e548
DW
3098
3099 // Need submit permission to submit an assignment
3100 require_capability('mod/assign:submit', $this->context);
94f26900 3101 require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
bbd0e548
DW
3102 require_sesskey();
3103
94f26900
DW
3104 $data = new stdClass();
3105 $adminconfig = $this->get_admin_config();
3106 $requiresubmissionstatement = !empty($adminconfig->requiresubmissionstatement) ||
3107 $this->get_instance()->requiresubmissionstatement;
3108
3109 $submissionstatement = '';
3110 if (!empty($adminconfig->submissionstatement)) {
3111 $submissionstatement = $adminconfig->submissionstatement;
3112 }
3113
3114 if ($mform == null) {
3115 $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
3116 $submissionstatement,
3117 $this->get_course_module()->id,
3118 $data));
3119 }
3120
3121 $data = $mform->get_data();
3122 if (!$mform->is_cancelled()) {
3123 if ($mform->get_data() == false) {
3124 return false;
bbd0e548 3125 }
12a1a0da
DW
3126 if ($this->get_instance()->teamsubmission) {
3127 $submission = $this->get_group_submission($USER->id, 0, true);
3128 } else {
3129 $submission = $this->get_user_submission($USER->id, true);
3130 }
3131
94f26900
DW
3132 if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
3133 // Give each submission plugin a chance to process the submission
3134 $plugins = $this->get_submission_plugins();
3135 foreach ($plugins as $plugin) {
3136 $plugin->submit_for_grading();
3137 }
bbd0e548 3138
94f26900 3139 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
12a1a0da 3140 $this->update_submission($submission, $USER->id, true, $this->get_instance()->teamsubmission);
79ed4d84
DW
3141 $completion = new completion_info($this->get_course());
3142 if ($completion->is_enabled($this->get_course_module()) && $this->get_instance()->completionsubmit) {
3143 $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id);
3144 }
3145
94f26900
DW
3146 if (isset($data->submissionstatement)) {
3147 $this->add_to_log('submission statement accepted', get_string('submissionstatementacceptedlog', 'mod_assign', fullname($USER)));
3148 }
3149 $this->add_to_log('submit for grading', $this->format_submission_for_log($submission));
3150 $this->notify_graders($submission);
3151 $this->notify_student_submission_receipt($submission);
3152 }
bbd0e548 3153 }
94f26900 3154 return true;
bbd0e548
DW
3155 }
3156
9e795179
DW
3157 /**
3158 * save the extension date for a single user
3159 *
3160 * @param int $userid The user id
3161 * @param mixed $extensionduedate Either an integer date or null
3162 * @return boolean
3163 */
3164 private function save_user_extension($userid, $extensionduedate) {
3165 global $DB;
3166
3167 $grade = $this->get_user_grade($userid, true);
3168 $grade->extensionduedate = $extensionduedate;
3169 $grade->timemodified = time();
3170
3171 $result = $DB->update_record('assign_grades', $grade);
3172
3173 if ($result) {
3174 $this->add_to_log('grant extension', $this->format_grade_for_log($grade));
3175 }
3176 return $result;
3177 }
3178
3179 /**
3180 * save extension date
3181 *
3182 * @param moodleform $mform The submitted form
3183 * @return boolean
3184 */
3185 private function process_save_extension(& $mform) {
3186 global $DB, $CFG;
3187
3188 // Include extension form.
3189 require_once($CFG->dirroot . '/mod/assign/extensionform.php');
3190
3191 // Need submit permission to submit an assignment.
3192 require_capability('mod/assign:grantextension', $this->context);
3193
3194 $batchusers = optional_param('selectedusers', '', PARAM_TEXT);
3195 $userid = 0;
3196 if (!$batchusers) {
3197 $userid = required_param('userid', PARAM_INT);
3198 $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
3199 }
3200 $mform = new mod_assign_extension_form(null, array($this->get_course_module()->id,
3201 $userid,
3202 $batchusers,
3203 $this->get_instance(),
3204 null));
3205
3206 if ($mform->is_cancelled()) {
3207 return true;
3208 }
3209
3210 if ($formdata = $mform->get_data()) {
3211 if ($batchusers) {
3212 $users = explode(',', $batchusers);
3213 $result = true;
3214 foreach ($users as $userid) {
3215 $result = $this->save_user_extension($userid, $formdata->extensionduedate) && $result;
3216 }
3217 return $result;
3218 } else {
3219 return $this->save_user_extension($userid, $formdata->extensionduedate);
3220 }
3221 }
3222 return false;
3223 }
3224
3225
bf78ebd6
DW
3226 /**
3227 * save quick grades
3228 *
2a4fbc32 3229 * @return string The result of the save operation
bf78ebd6
DW
3230 */
3231 private function process_save_quick_grades() {
3232 global $USER, $DB, $CFG;
3233
3234 // Need grade permission
3235 require_capability('mod/assign:grade', $this->context);
3236
3237 // make sure advanced grading is disabled
3238 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
3239 $controller = $gradingmanager->get_active_controller();
3240 if (!empty($controller)) {
2a4fbc32 3241 return get_string('errorquickgradingvsadvancedgrading', 'assign');
bf78ebd6
DW
3242 }
3243
3244 $users = array();
3245 // first check all the last modified values
2c6a3dbf
DW
3246 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
3247 $participants = $this->list_participants($currentgroup, true);
3248
2a4fbc32 3249 // gets a list of possible users and look for values based upon that.
2c6a3dbf
DW
3250 foreach ($participants as $userid => $unused) {
3251 $modified = optional_param('grademodified_' . $userid, -1, PARAM_INT);
3252 if ($modified >= 0) {
bf78ebd6
DW
3253 // gather the userid, updated grade and last modified value
3254 $record = new stdClass();
2c6a3dbf 3255 $record->userid = $userid;
4913af93 3256 $record->grade = unformat_float(required_param('quickgrade_' . $record->userid, PARAM_TEXT));
2c6a3dbf
DW
3257 $record->lastmodified = $modified;
3258 $record->gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, array($userid));
3259 $users[$userid] = $record;
bf78ebd6
DW
3260 }
3261 }
2a4fbc32
SH
3262 if (empty($users)) {
3263 // Quick check to see whether we have any users to update and we don't
3264 return get_string('quickgradingchangessaved', 'assign'); // Technical lie
3265 }
bf78ebd6 3266
2a4fbc32
SH
3267 list($userids, $params) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED);
3268 $params['assignment'] = $this->get_instance()->id;
bf78ebd6 3269 // check them all for currency
2a4fbc32
SH
3270 $sql = 'SELECT u.id as userid, g.grade as grade, g.timemodified as lastmodified
3271 FROM {user} u
3272 LEFT JOIN {assign_grades} g ON u.id = g.userid AND g.assignment = :assignment
3273 WHERE u.id ' . $userids;
3274 $currentgrades = $DB->get_recordset_sql($sql, $params);
bf78ebd6
DW
3275
3276 $modifiedusers = array();
3277 foreach ($currentgrades as $current) {
3278 $modified = $users[(int)$current->userid];
2c6a3dbf 3279 $grade = $this->get_user_grade($userid, false);
bf78ebd6
DW
3280
3281 // check to see if the outcomes were modified
3282 if ($CFG->enableoutcomes) {
3283 foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
3284 $oldoutcome = $outcome->grades[$modified->userid]->grade;
4913af93 3285 $newoutcome = optional_param('outcome_' . $outcomeid . '_' . $modified->userid, -1, PARAM_FLOAT);
bf78ebd6
DW
3286 if ($oldoutcome != $newoutcome) {
3287 // can't check modified time for outcomes because it is not reported
3288 $modifiedusers[$modified->userid] = $modified;
3289 continue;
3290 }
3291 }
3292 }
3293
2c6a3dbf
DW
3294 // let plugins participate
3295 foreach ($this->feedbackplugins as $plugin) {
3296 if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
3297 if ($plugin->is_quickgrading_modified($modified->userid, $grade)) {
3298 if ((int)$current->lastmodified > (int)$modified->lastmodified) {
3299 return get_string('errorrecordmodified', 'assign');
3300 } else {
3301 $modifiedusers[$modified->userid] = $modified;
3302 continue;
3303 }
3304 }
3305 }
3306 }
3307
bf78ebd6
DW
3308
3309 if (($current->grade < 0 || $current->grade === NULL) &&
3310 ($modified->grade < 0 || $modified->grade === NULL)) {
3311 // different ways to indicate no grade
3312 continue;
3313 }
a13edd49
DW
3314 // Treat 0 and null as different values
3315 if ($current->grade !== null) {
3316 $current->grade = floatval($current->grade);
3317 }
3318 if ($current->grade !== $modified->grade) {
bf78ebd6 3319 // grade changed
09b46f0e
AA
3320 if ($this->grading_disabled($modified->userid)) {
3321 continue;
3322 }
bf78ebd6
DW
3323 if ((int)$current->lastmodified > (int)$modified->lastmodified) {
3324 // error - record has been modified since viewing the page
3325 return get_string('errorrecordmodified', 'assign');
3326 } else {
3327 $modifiedusers[$modified->userid] = $modified;
3328 }
3329 }
3330
3331 }
2a4fbc32 3332 $currentgrades->close();
bf78ebd6
DW
3333
3334 // ok - ready to process the updates
3335 foreach ($modifiedusers as $userid => $modified) {
3336 $grade = $this->get_user_grade($userid, true);
4913af93 3337 $grade->grade= grade_floatval(unformat_float($modified->grade));
bf78ebd6
DW
3338 $grade->grader= $USER->id;
3339
3340 $this->update_grade($grade);
3341
2c6a3dbf
DW
3342 // save plugins data
3343 foreach ($this->feedbackplugins as $plugin) {
3344 if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
3345 $plugin->save_quickgrading_changes($userid, $grade);
3346 }
3347 }
3348
bf78ebd6
DW
3349 // save outcomes
3350 if ($CFG->enableoutcomes) {
3351 $data = array();
3352 foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
3353 $oldoutcome = $outcome->grades[$modified->userid]->grade;
3354 $newoutcome = optional_param('outcome_' . $outcomeid . '_' . $modified->userid, -1, PARAM_INT);
3355 if ($oldoutcome != $newoutcome) {
3356 $data[$outcomeid] = $newoutcome;
3357 }
3358 }
3359 if (count($data) > 0) {
3360 grade_update_outcomes('mod/assign', $this->course->id, 'mod', 'assign', $this->get_instance()->id, $userid, $data);
3361 }
3362 }
3363
bf78ebd6
DW
3364 $this->add_to_log('grade submission', $this->format_grade_for_log($grade));
3365 }
3366
3367 return get_string('quickgradingchangessaved', 'assign');
3368 }
3369
b473171a
DW
3370 /**
3371 * Reveal student identities to markers (and the gradebook)
3372 *
3373 * @return void
3374 */
3375 private function process_reveal_identities() {
3376 global $DB, $CFG;
3377
3378 require_capability('mod/assign:revealidentities', $this->context);
3379 if (!confirm_sesskey()) {
3380 return false;
3381 }
3382
3383 // Update the assignment record.
3384 $update = new stdClass();
3385 $update->id = $this->get_instance()->id;
3386 $update->revealidentities = 1;
3387 $DB->update_record('assign', $update);
3388
3389 // Refresh the instance data.
3390 $this->instance = null;
3391
3392 // Release the grades to the gradebook.
3393 // First create the column in the gradebook.
3394 $this->update_gradebook(false, $this->get_course_module()->id);
3395
3396 // Now release all grades.
3397
3398 $adminconfig = $this->get_admin_config();
3399 $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3400 $grades = $DB->get_records('assign_grades', array('assignment'=>$this->get_instance()->id));
3401
3402 $plugin = $this->get_feedback_plugin_by_type($gradebookplugin);
3403
3404 foreach ($grades as $grade) {
3405 // Fetch any comments for this student.
3406 if ($plugin && $plugin->is_enabled() && $plugin->is_visible()) {
3407 $grade->feedbacktext = $plugin->text_for_gradebook($grade);
3408 $grade->feedbackformat = $plugin->format_for_gradebook($grade);
3409 }
3410 $this->gradebook_item_update(NULL, $grade);
3411 }
3412
3413 $this->add_to_log('reveal identities', get_string('revealidentities', 'assign'));
3414 }
3415
3416
bbd0e548
DW
3417 /**
3418 * save grading options
3419 *
3420 * @return void
3421 */
3422 private function process_save_grading_options() {
3423 global $USER, $CFG;
3424
3425 // Include grading options form
3426 require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
3427
3428 // Need submit permission to submit an assignment
3429 require_capability('mod/assign:grade', $this->context);
3430
44e2f0fe
DW
3431 $mform = new mod_assign_grading_options_form(null, array('cm'=>$this->get_course_module()->id,
3432 'contextid'=>$this->context->id,
3433 'userid'=>$USER->id,
3434 'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
3435 'showquickgrading'=>false));
bbd0e548
DW
3436 if ($formdata = $mform->get_data()) {
3437 set_user_preference('assign_perpage', $formdata->perpage);
3438 set_user_preference('assign_filter', $formdata->filter);
3439 }
3440 }
3441
3442 /**
3443 * Take a grade object and print a short summary for the log file.
3444 * The size limit for the log file is 255 characters, so be careful not
3445 * to include too much information.
3446 *
3447 * @param stdClass $grade
3448 * @return string
3449 */
3450 private function format_grade_for_log(stdClass $grade) {
3451 global $DB;
3452
3453 $user = $DB->get_record('user', array('id' => $grade->userid), '*', MUST_EXIST);
3454
3455 $info = get_string('gradestudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
3456 if ($grade->grade != '') {
bf78ebd6 3457 $info .= get_string('grade') . ': ' . $this->display_grade($grade->grade, false) . '. ';
bbd0e548
DW
3458 } else {
3459 $info .= get_string('nograde', 'assign');
3460 }
3461 if ($grade->locked) {
3462 $info .= get_string('submissionslocked', 'assign') . '. ';
3463 }
9e795179
DW
3464 if ($grade->extensionduedate) {
3465 $info .= get_string('userextensiondate', 'assign', userdate($grade->extensionduedate));
3466 }
bbd0e548
DW
3467 return $info;
3468 }
3469
3470 /**
3471 * Take a submission object and print a short summary for the log file.
3472 * The size limit for the log file is 255 characters, so be careful not
3473 * to include too much information.
3474 *
3475 * @param stdClass $submission
3476 * @return string
3477 */
3478 private function format_submission_for_log(stdClass $submission) {
3479 $info = '';
3480 $info .= get_string('submissionstatus', 'assign') . ': ' . get_string('submissionstatus_' . $submission->status, 'assign') . '. <br>';
3481 // format_for_log here iterating every single log INFO from either submission or grade in every assignment plugin
3482
3483 foreach ($this->submissionplugins as $plugin) {
3484 if ($plugin->is_enabled() && $plugin->is_visible()) {
3485
3486
3487 $info .= "<br>" . $plugin->format_for_log($submission);
3488 }
3489 }
3490
3491
3492 return $info;
3493 }
3494
3495 /**
3496 * save assignment submission
3497 *
3498 * @param moodleform $mform
3499 * @return bool
3500 */
3501 private function process_save_submission(&$mform) {
3502 global $USER, $CFG;
3503
3504 // Include submission form
3505 require_once($CFG->dirroot . '/mod/assign/submission_form.php');
3506
3507 // Need submit permission to submit an assignment
3508 require_capability('mod/assign:submit', $this->context);
3509 require_sesskey();
3510
3511 $data = new stdClass();
3512 $mform = new mod_assign_submission_form(null, array($this, $data));
3513 if ($mform->is_cancelled()) {
3514 return true;
3515 }
3516 if ($data = $mform->get_data()) {
12a1a0da
DW
3517 if ($this->get_instance()->teamsubmission) {
3518 $submission = $this->get_group_submission($USER->id, 0, true);
3519 } else {
3520 $submission = $this->get_user_submission($USER->id, true);
3521 }
3522 if ($this->get_instance()->submissiondrafts) {
3523 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
3524 } else {
3525 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
3526 }
3527
bbd0e548
DW
3528 $grade = $this->get_user_grade($USER->id, false); // get the grade to check if it is locked
3529 if ($grade && $grade->locked) {
3530 print_error('submissionslocked', 'assign');
3531 return true;
3532 }
3533
3534
3535 foreach ($this->submissionplugins as $plugin) {
3536 if ($plugin->is_enabled()) {
3537 if (!$plugin->save($submission, $data)) {
3538 print_error($plugin->get_error());
3539 }
3540 }
3541 }
3542
12a1a0da 3543 $this->update_submission($submission, $USER->id, true, $this->get_instance()->teamsubmission);
bbd0e548
DW
3544
3545 // Logging
94f26900
DW
3546 if (isset($data->submissionstatement)) {
3547 $this->add_to_log('submission statement accepted', get_string('submissionstatementacceptedlog', 'mod_assign', fullname($USER)));
3548 }
bbd0e548
DW
3549 $this->add_to_log('submit', $this->format_submission_for_log($submission));
3550
79ed4d84
DW
3551 $complete = COMPLETION_INCOMPLETE;
3552 if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
3553 $complete = COMPLETION_COMPLETE;
3554 }
3555 $completion = new completion_info($this->get_course());
3556 if ($completion->is_enabled($this->get_course_module()) && $this->get_instance()->completionsubmit) {
3557 $completion->update_state($this->get_course_module(), $complete, $USER->id);
3558 }
3559
bbd0e548 3560 if (!$this->get_instance()->submissiondrafts) {
c1d09c6f
RK
3561 $this->notify_student_submission_receipt($submission);
3562 $this->notify_graders($submission);
bbd0e548
DW
3563 }
3564 return true;
3565 }
3566 return false;
3567 }
3568
bbd0e548
DW
3569
3570 /**
3571 * Determine if this users grade is locked or overridden
3572 *
3573 * @param int $userid - The student userid
3574 * @return bool $gradingdisabled
3575 */
09b46f0e 3576 public function grading_disabled($userid) {
bbd0e548
DW
3577 global $CFG;
3578
3579 $gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, array($userid));
3580 if (!$gradinginfo) {
3581 return false;
3582 }
3583
3584 if (!isset($gradinginfo->items[0]->grades[$userid])) {
3585 return false;
3586 }
3587 $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked || $gradinginfo->items[0]->grades[$userid]->overridden;
3588 return $gradingdisabled;
3589 }
3590
3591
3592 /**
3593 * Get an instance of a grading form if advanced grading is enabled
3594 * This is specific to the assignment, marker and student
3595 *
3596 * @param int $userid - The student userid
3597 * @param bool $gradingdisabled
3598 * @return mixed gradingform_instance|null $g