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