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