MDL-12182 More grade_item unit tests
[moodle.git] / mod / assignment / lib.php
CommitLineData
5c6b657a 1<?PHP // $Id$
b0f2597e 2/**
3 * assignment_base is the base class for assignment types
4 *
5 * This class provides all the functionality for an assignment
6 */
04eba58f 7
1884f2a6 8DEFINE ('ASSIGNMENT_COUNT_WORDS', 1);
9DEFINE ('ASSIGNMENT_COUNT_LETTERS', 2);
d699cd1e 10
4909e176 11if (!isset($CFG->assignment_maxbytes)) {
12 set_config("assignment_maxbytes", 1024000); // Default maximum size for all assignments
04eba58f 13}
1884f2a6 14if (!isset($CFG->assignment_itemstocount)) {
15 set_config("assignment_itemstocount", ASSIGNMENT_COUNT_WORDS); // Default item to count
16}
04eba58f 17
7af1e882 18/**
b0f2597e 19 * Standard base class for all assignment submodules (assignment types).
b0f2597e 20 */
21class assignment_base {
22
23 var $cm;
24 var $course;
25 var $assignment;
7af1e882 26 var $strassignment;
27 var $strassignments;
28 var $strsubmissions;
29 var $strlastmodified;
7af1e882 30 var $pagetitle;
31 var $currentgroup;
32 var $usehtmleditor;
33 var $defaultformat;
55b4d096 34 var $context;
b0f2597e 35
36 /**
37 * Constructor for the base assignment class
38 *
39 * Constructor for the base assignment class.
40 * If cmid is set create the cm, course, assignment objects.
7af1e882 41 * If the assignment is hidden and the user is not a teacher then
42 * this prints a page header and notice.
b0f2597e 43 *
44 * @param cmid integer, the current course module id - not set for new assignments
73097f07 45 * @param assignment object, usually null, but if we have it we pass it to save db access
7af1e882 46 * @param cm object, usually null, but if we have it we pass it to save db access
47 * @param course object, usually null, but if we have it we pass it to save db access
b0f2597e 48 */
7bddd4b7 49 function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
50 if ($cmid == 'staticonly') {
51 //use static functions only!
52 return;
53 }
b0f2597e 54
55 global $CFG;
56
7bddd4b7 57 if ($cm) {
58 $this->cm = $cm;
59 } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
7bddd4b7 60 error('Course Module ID was incorrect');
61 }
04eba58f 62
7bddd4b7 63 $this->context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
55b4d096 64
7bddd4b7 65 if ($course) {
66 $this->course = $course;
67 } else if (! $this->course = get_record('course', 'id', $this->cm->course)) {
68 error('Course is misconfigured');
69 }
04eba58f 70
7bddd4b7 71 if ($assignment) {
72 $this->assignment = $assignment;
73 } else if (! $this->assignment = get_record('assignment', 'id', $this->cm->instance)) {
74 error('assignment ID was incorrect');
75 }
76
b5ebd096 77 $this->assignment->cmidnumber = $this->cm->id; // compatibility with modedit assignment obj
78 $this->assignment->courseid = $this->course->id; // compatibility with modedit assignment obj
e6a4906b 79
7bddd4b7 80 $this->strassignment = get_string('modulename', 'assignment');
81 $this->strassignments = get_string('modulenameplural', 'assignment');
82 $this->strsubmissions = get_string('submissions', 'assignment');
83 $this->strlastmodified = get_string('lastmodified');
7bddd4b7 84 $this->pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment.': '.format_string($this->assignment->name,true));
85
86 // visibility
87 $context = get_context_instance(CONTEXT_MODULE, $cmid);
88 if (!$this->cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) {
89 $pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment);
38e179a4 90 $navigation = build_navigation('', $this->cm);
45fa3412 91
38e179a4 92 print_header($pagetitle, $this->course->fullname, $navigation,
7bddd4b7 93 "", "", true, '', navmenu($this->course, $this->cm));
94 notice(get_string("activityiscurrentlyhidden"), "$CFG->wwwroot/course/view.php?id={$this->course->id}");
73097f07 95 }
ba3dc7b4 96 $this->currentgroup = groups_get_activity_group($this->cm);
e6a4906b 97
73097f07 98 /// Set up things for a HTML editor if it's needed
99 if ($this->usehtmleditor = can_use_html_editor()) {
100 $this->defaultformat = FORMAT_HTML;
101 } else {
102 $this->defaultformat = FORMAT_MOODLE;
e6a4906b 103 }
104 }
105
7af1e882 106 /**
107 * Display the assignment, used by view.php
108 *
109 * This in turn calls the methods producing individual parts of the page
b0f2597e 110 */
b0f2597e 111 function view() {
45fa3412 112
dabfd0ed 113 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
0468976c 114 require_capability('mod/assignment:view', $context);
45fa3412 115
116 add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
b0f2597e 117 $this->assignment->id, $this->cm->id);
04eba58f 118
73097f07 119 $this->view_header();
04eba58f 120
f77cfb73 121 $this->view_intro();
04eba58f 122
f77cfb73 123 $this->view_dates();
04eba58f 124
b0f2597e 125 $this->view_feedback();
126
f77cfb73 127 $this->view_footer();
36eb856f 128 }
129
7af1e882 130 /**
131 * Display the header and top of a page
132 *
133 * (this doesn't change much for assignment types)
134 * This is used by the view() method to print the header of view.php but
135 * it can be used on other pages in which case the string to denote the
136 * page in the navigation trail should be passed as an argument
137 *
138 * @param $subpage string Description of subpage to be used in navigation trail
73097f07 139 */
140 function view_header($subpage='') {
141
142 global $CFG;
143
45fa3412 144
73097f07 145 if ($subpage) {
38e179a4 146 $navigation = build_navigation($subpage, $this->cm);
73097f07 147 } else {
38e179a4 148 $navigation = build_navigation('', $this->cm);
73097f07 149 }
45fa3412 150
45fa3412 151 print_header($this->pagetitle, $this->course->fullname, $navigation, '', '',
152 true, update_module_button($this->cm->id, $this->course->id, $this->strassignment),
73097f07 153 navmenu($this->course, $this->cm));
154
ba3dc7b4 155 $groupmode = groups_get_activity_groupmode($this->cm);
57b6a704 156 $this->currentgroup = groups_get_activity_group($this->cm);
ba3dc7b4 157 groups_print_activity_menu($this->cm, 'view.php?id=' . $this->cm->id);
45fa3412 158
73097f07 159 echo '<div class="reportlink">'.$this->submittedlink().'</div>';
7bddd4b7 160 echo '<div class="clearer"></div>';
73097f07 161 }
162
163
7af1e882 164 /**
f77cfb73 165 * Display the assignment intro
7af1e882 166 *
167 * This will most likely be extended by assignment type plug-ins
168 * The default implementation prints the assignment description in a box
f77cfb73 169 */
170 function view_intro() {
32776fef 171 print_simple_box_start('center', '', '', 0, 'generalbox', 'intro');
1e4343a0 172 $formatoptions = new stdClass;
173 $formatoptions->noclean = true;
174 echo format_text($this->assignment->description, $this->assignment->format, $formatoptions);
f77cfb73 175 print_simple_box_end();
176 }
177
7af1e882 178 /**
f77cfb73 179 * Display the assignment dates
7af1e882 180 *
181 * Prints the assignment start and end dates in a box.
182 * This will be suitable for most assignment types
f77cfb73 183 */
184 function view_dates() {
185 if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
186 return;
187 }
188
32776fef 189 print_simple_box_start('center', '', '', 0, 'generalbox', 'dates');
f77cfb73 190 echo '<table>';
191 if ($this->assignment->timeavailable) {
192 echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
193 echo ' <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
194 }
195 if ($this->assignment->timedue) {
196 echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
197 echo ' <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
198 }
199 echo '</table>';
200 print_simple_box_end();
201 }
202
203
7af1e882 204 /**
205 * Display the bottom and footer of a page
206 *
207 * This default method just prints the footer.
208 * This will be suitable for most assignment types
73097f07 209 */
210 function view_footer() {
211 print_footer($this->course);
212 }
213
7af1e882 214 /**
215 * Display the feedback to the student
216 *
217 * This default method prints the teacher picture and name, date when marked,
ea6432fe 218 * grade and teacher submissioncomment.
7af1e882 219 *
220 * @param $submission object The submission object or NULL in which case it will be loaded
221 */
73097f07 222 function view_feedback($submission=NULL) {
5978010d 223 global $USER, $CFG;
224 require_once($CFG->libdir.'/gradelib.php');
225
226 if (!has_capability('mod/assignment:submit', $this->context, $USER->id, false)) {
227 // can not submit assignments -> no feedback
228 return;
229 }
e6a4906b 230
73097f07 231 if (!$submission) { /// Get submission for this assignment
232 $submission = $this->get_submission($USER->id);
70b2c772 233 }
234
5978010d 235 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $USER->id);
236 $item = $grading_info->items[0];
237 $grade = $item->grades[$USER->id];
238
239 if ($grade->hidden or $grade->grade === false) { // hidden or error
240 return;
241 }
242
243 if ($grade->grade === null and empty($feedback)) { /// Nothing to show yet
70b2c772 244 return;
9c48354d 245 }
e6a4906b 246
ced5ee59 247 $graded_date = $grade->dategraded;
248 $graded_by = $grade->usermodified;
e6a4906b 249
5978010d 250 /// We need the teacher info
251 $teacher = get_record('user', 'id', $graded_by);
252
b0f2597e 253 /// Print the feedback
5978010d 254 print_heading(get_string('feedbackfromteacher', 'assignment', $this->course->teacher)); // TODO: fix teacher string
6d4ecaec 255
b0f2597e 256 echo '<table cellspacing="0" class="feedback">';
257
258 echo '<tr>';
259 echo '<td class="left picture">';
5978010d 260 if ($teacher) {
261 print_user_picture($teacher->id, $this->course->id, $teacher->picture);
262 }
b0f2597e 263 echo '</td>';
6d4ecaec 264 echo '<td class="topic">';
70b2c772 265 echo '<div class="from">';
5978010d 266 if ($teacher) {
267 echo '<div class="fullname">'.fullname($teacher).'</div>';
268 }
269 echo '<div class="time">'.userdate($graded_date).'</div>';
70b2c772 270 echo '</div>';
b0f2597e 271 echo '</td>';
272 echo '</tr>';
273
274 echo '<tr>';
275 echo '<td class="left side">&nbsp;</td>';
6d4ecaec 276 echo '<td class="content">';
5978010d 277 echo '<div class="grade">';
278 echo get_string("grade").': '.$grade->str_grade;
279 echo '</div>';
280 echo '<div class="clearer"></div>';
dcd338ff 281
6d4ecaec 282 echo '<div class="comment">';
5978010d 283 echo $grade->str_feedback;
6d4ecaec 284 echo '</div>';
b0f2597e 285 echo '</tr>';
286
287 echo '</table>';
e6a4906b 288 }
e6a4906b 289
45fa3412 290 /**
ba16713f 291 * Returns a link with info about the state of the assignment submissions
7af1e882 292 *
293 * This is used by view_header to put this link at the top right of the page.
294 * For teachers it gives the number of submitted assignments with a link
295 * For students it gives the time of their submission.
296 * This will be suitable for most assignment types.
297 * @return string
ba16713f 298 */
299 function submittedlink() {
300 global $USER;
301
302 $submitted = '';
303
bbbf2d40 304 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
1648afb2 305 if (has_capability('mod/assignment:grade', $context)) {
bbbf2d40 306
307 // if this user can mark and is put in a group
308 // then he can only see/mark submission in his own groups
ba3dc7b4 309 if (!has_capability('moodle/course:managegroups', $context) and (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS)) {
bbbf2d40 310 $count = $this->count_real_submissions($this->currentgroup); // Only their groups
ba16713f 311 } else {
57b6a704 312 $count = $this->count_real_submissions($this->currentgroup); // Everyone
ba16713f 313 }
314 $submitted = '<a href="submissions.php?id='.$this->cm->id.'">'.
315 get_string('viewsubmissions', 'assignment', $count).'</a>';
316 } else {
86a1ba04 317 if (!empty($USER->id)) {
ba16713f 318 if ($submission = $this->get_submission($USER->id)) {
319 if ($submission->timemodified) {
1e4343a0 320 if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
ba16713f 321 $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
322 } else {
323 $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
324 }
325 }
326 }
327 }
328 }
329
330 return $submitted;
331 }
332
333
436cfa9f 334 function setup_elements(&$mform) {
45fa3412 335
436cfa9f 336 }
337
7af1e882 338 /**
339 * Create a new assignment activity
340 *
341 * Given an object containing all the necessary data,
342 * (defined by the form in mod.html) this function
343 * will create a new instance and return the id number
344 * of the new instance.
345 * The due data is added to the calendar
346 * This is common to all assignment types.
347 *
348 * @param $assignment object The data from the form on mod.html
349 * @return int The id of the assignment
350 */
b0f2597e 351 function add_instance($assignment) {
7bddd4b7 352 global $COURSE;
b0f2597e 353
354 $assignment->timemodified = time();
7bddd4b7 355 $assignment->courseid = $assignment->course;
38147229 356
736f191c 357 if ($returnid = insert_record("assignment", $assignment)) {
7bddd4b7 358 $assignment->id = $returnid;
736f191c 359
1e4343a0 360 if ($assignment->timedue) {
7bddd4b7 361 $event = new object();
1e4343a0 362 $event->name = $assignment->name;
363 $event->description = $assignment->description;
364 $event->courseid = $assignment->course;
365 $event->groupid = 0;
366 $event->userid = 0;
367 $event->modulename = 'assignment';
368 $event->instance = $returnid;
369 $event->eventtype = 'due';
370 $event->timestart = $assignment->timedue;
371 $event->timeduration = 0;
736f191c 372
1e4343a0 373 add_event($event);
374 }
7bddd4b7 375
376 $assignment = stripslashes_recursive($assignment);
612607bd 377 assignment_grade_item_update($assignment);
7bddd4b7 378
736f191c 379 }
380
7bddd4b7 381
736f191c 382 return $returnid;
b0f2597e 383 }
d699cd1e 384
7af1e882 385 /**
386 * Deletes an assignment activity
387 *
1f8c6549 388 * Deletes all database records, files and calendar events for this assignment.
7af1e882 389 * @param $assignment object The assignment to be deleted
390 * @return boolean False indicates error
391 */
b0f2597e 392 function delete_instance($assignment) {
1f8c6549 393 global $CFG;
394
ada917d5 395 $assignment->courseid = $assignment->course;
396
736f191c 397 $result = true;
398
399 if (! delete_records('assignment_submissions', 'assignment', $assignment->id)) {
400 $result = false;
401 }
402
403 if (! delete_records('assignment', 'id', $assignment->id)) {
404 $result = false;
405 }
406
407 if (! delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id)) {
408 $result = false;
409 }
45fa3412 410
1f8c6549 411 // delete file area with all attachments - ignore errors
412 require_once($CFG->libdir.'/filelib.php');
413 fulldelete($CFG->dataroot.'/'.$assignment->course.'/'.$CFG->moddata.'/assignment/'.$assignment->id);
414
45fa3412 415 assignment_grade_item_delete($assignment);
ada917d5 416
736f191c 417 return $result;
b0f2597e 418 }
d699cd1e 419
7af1e882 420 /**
421 * Updates a new assignment activity
422 *
423 * Given an object containing all the necessary data,
424 * (defined by the form in mod.html) this function
425 * will update the assignment instance and return the id number
426 * The due date is updated in the calendar
427 * This is common to all assignment types.
428 *
429 * @param $assignment object The data from the form on mod.html
430 * @return int The assignment id
431 */
b0f2597e 432 function update_instance($assignment) {
7bddd4b7 433 global $COURSE;
b0f2597e 434
38147229 435 $assignment->timemodified = time();
38147229 436
b0f2597e 437 $assignment->id = $assignment->instance;
7bddd4b7 438 $assignment->courseid = $assignment->course;
736f191c 439
7bddd4b7 440 if (!update_record('assignment', $assignment)) {
441 return false;
442 }
736f191c 443
7bddd4b7 444 if ($assignment->timedue) {
445 $event = new object();
736f191c 446
7bddd4b7 447 if ($event->id = get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id)) {
736f191c 448
7bddd4b7 449 $event->name = $assignment->name;
450 $event->description = $assignment->description;
451 $event->timestart = $assignment->timedue;
736f191c 452
7bddd4b7 453 update_event($event);
47263937 454 } else {
7bddd4b7 455 $event = new object();
456 $event->name = $assignment->name;
457 $event->description = $assignment->description;
458 $event->courseid = $assignment->course;
459 $event->groupid = 0;
460 $event->userid = 0;
461 $event->modulename = 'assignment';
462 $event->instance = $assignment->id;
463 $event->eventtype = 'due';
464 $event->timestart = $assignment->timedue;
465 $event->timeduration = 0;
466
467 add_event($event);
736f191c 468 }
7bddd4b7 469 } else {
470 delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id);
736f191c 471 }
472
7bddd4b7 473 // get existing grade item
474 $assignment = stripslashes_recursive($assignment);
de420c11 475
45fa3412 476 assignment_grade_item_update($assignment);
7bddd4b7 477
478 return true;
479 }
480
481 /**
45fa3412 482 * Update grade item for this submission.
7bddd4b7 483 */
45fa3412 484 function update_grade($submission) {
612607bd 485 assignment_update_grades($this->assignment, $submission->userid);
b0f2597e 486 }
487
7af1e882 488 /**
b0f2597e 489 * Top-level function for handling of submissions called by submissions.php
7af1e882 490 *
491 * This is for handling the teacher interaction with the grading interface
492 * This should be suitable for most assignment types.
493 *
494 * @param $mode string Specifies the kind of teacher interaction taking place
b0f2597e 495 */
496 function submissions($mode) {
9bf660b3 497 ///The main switch is changed to facilitate
498 ///1) Batch fast grading
499 ///2) Skip to the next one on the popup
500 ///3) Save and Skip to the next one on the popup
45fa3412 501
9bf660b3 502 //make user global so we can use the id
503 global $USER;
45fa3412 504
b0f2597e 505 switch ($mode) {
506 case 'grade': // We are in a popup window grading
507 if ($submission = $this->process_feedback()) {
5a36be8c 508 //IE needs proper header with encoding
509 print_header(get_string('feedback', 'assignment').':'.format_string($this->assignment->name));
b0f2597e 510 print_heading(get_string('changessaved'));
73963212 511 print $this->update_main_listing($submission);
b0f2597e 512 }
989a0a52 513 close_window();
b0f2597e 514 break;
9cc9b7c1 515
b0f2597e 516 case 'single': // We are in a popup window displaying submission
517 $this->display_submission();
518 break;
a56d79cd 519
1648afb2 520 case 'all': // Main window, display everything
b0f2597e 521 $this->display_submissions();
522 break;
082215e6 523
9bf660b3 524 case 'fastgrade':
082215e6 525 ///do the fast grading stuff - this process should work for all 3 subclasses
39e11905 526 $grading = false;
16907e53 527 $commenting = false;
39e11905 528 $col = false;
ea6432fe 529 if (isset($_POST['submissioncomment'])) {
530 $col = 'submissioncomment';
16907e53 531 $commenting = true;
532 }
533 if (isset($_POST['menu'])) {
39e11905 534 $col = 'menu';
16907e53 535 $grading = true;
536 }
39e11905 537 if (!$col) {
ea6432fe 538 //both submissioncomment and grade columns collapsed..
45fa3412 539 $this->display_submissions();
16907e53 540 break;
541 }
39e11905 542 foreach ($_POST[$col] as $id => $unusedvalue){
77f4b17b 543
544 $id = (int)$id; //clean parameter name
cc03871b 545
546 $this->process_outcomes($id);
547
39e11905 548 if (!$submission = $this->get_submission($id)) {
549 $submission = $this->prepare_new_submission($id);
550 $newsubmission = true;
551 } else {
552 $newsubmission = false;
553 }
554 unset($submission->data1); // Don't need to update this.
555 unset($submission->data2); // Don't need to update this.
16907e53 556
9bf660b3 557 //for fast grade, we need to check if any changes take place
16907e53 558 $updatedb = false;
559
560 if ($grading) {
561 $grade = $_POST['menu'][$id];
39e11905 562 $updatedb = $updatedb || ($submission->grade != $grade);
563 $submission->grade = $grade;
16907e53 564 } else {
39e11905 565 if (!$newsubmission) {
566 unset($submission->grade); // Don't need to update this.
567 }
16907e53 568 }
569 if ($commenting) {
ea6432fe 570 $commentvalue = trim($_POST['submissioncomment'][$id]);
571 $updatedb = $updatedb || ($submission->submissioncomment != stripslashes($commentvalue));
572 $submission->submissioncomment = $commentvalue;
16907e53 573 } else {
ea6432fe 574 unset($submission->submissioncomment); // Don't need to update this.
9bf660b3 575 }
576
39e11905 577 $submission->teacher = $USER->id;
578 $submission->mailed = $updatedb?0:$submission->mailed;//only change if it's an update
579 $submission->timemarked = time();
580
581 //if it is not an update, we don't change the last modified time etc.
ea6432fe 582 //this will also not write into database if no submissioncomment and grade is entered.
39e11905 583
16907e53 584 if ($updatedb){
39e11905 585 if ($newsubmission) {
7bddd4b7 586 if (!$sid = insert_record('assignment_submissions', $submission)) {
39e11905 587 return false;
588 }
7bddd4b7 589 $submission->id = $sid;
39e11905 590 } else {
591 if (!update_record('assignment_submissions', $submission)) {
592 return false;
593 }
7bddd4b7 594 }
595
596 // triger grade event
45fa3412 597 $this->update_grade($submission);
7bddd4b7 598
39e11905 599 //add to log only if updating
45fa3412 600 add_to_log($this->course->id, 'assignment', 'update grades',
601 'submissions.php?id='.$this->assignment->id.'&user='.$submission->userid,
602 $submission->userid, $this->cm->id);
9bf660b3 603 }
45fa3412 604
605 }
cc03871b 606
fb81abe1 607 $message = notify(get_string('changessaved'), 'notifysuccess', 'center', true);
608
609 $this->display_submissions($message);
9bf660b3 610 break;
39e11905 611
612
9bf660b3 613 case 'next':
614 /// We are currently in pop up, but we want to skip to next one without saving.
615 /// This turns out to be similar to a single case
616 /// The URL used is for the next submission.
45fa3412 617
9bf660b3 618 $this->display_submission();
619 break;
45fa3412 620
9bf660b3 621 case 'saveandnext':
622 ///We are in pop up. save the current one and go to the next one.
623 //first we save the current changes
624 if ($submission = $this->process_feedback()) {
625 //print_heading(get_string('changessaved'));
73963212 626 $extra_javascript = $this->update_main_listing($submission);
9bf660b3 627 }
45fa3412 628
9bf660b3 629 //then we display the next submission
73963212 630 $this->display_submission($extra_javascript);
9bf660b3 631 break;
45fa3412 632
9bf660b3 633 default:
634 echo "something seriously is wrong!!";
45fa3412 635 break;
a56d79cd 636 }
b0f2597e 637 }
45fa3412 638
7af1e882 639 /**
640 * Helper method updating the listing on the main script from popup using javascript
641 *
642 * @param $submission object The submission whose data is to be updated on the main page
643 */
be86672d 644 function update_main_listing($submission) {
fbaa56b2 645 global $SESSION, $CFG;
45fa3412 646
73963212 647 $output = '';
648
9bf660b3 649 $perpage = get_user_preferences('assignment_perpage', 10);
be86672d 650
9bf660b3 651 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
45fa3412 652
be86672d 653 /// Run some Javascript to try and update the parent page
73963212 654 $output .= '<script type="text/javascript">'."\n<!--\n";
ea6432fe 655 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
9bf660b3 656 if ($quickgrade){
16fc2088 657 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
ea6432fe 658 .trim($submission->submissioncomment).'";'."\n";
9bf660b3 659 } else {
73963212 660 $output.= 'opener.document.getElementById("com'.$submission->userid.
ea6432fe 661 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
9bf660b3 662 }
be86672d 663 }
9bf660b3 664
665 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
666 //echo optional_param('menuindex');
667 if ($quickgrade){
16fc2088 668 $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
669 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
9bf660b3 670 } else {
73963212 671 $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
9bf660b3 672 $this->display_grade($submission->grade)."\";\n";
45fa3412 673 }
674 }
9bf660b3 675 //need to add student's assignments in there too.
73097f07 676 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
677 $submission->timemodified) {
73963212 678 $output.= 'opener.document.getElementById("ts'.$submission->userid.
3a935caf 679 '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
be86672d 680 }
45fa3412 681
73097f07 682 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
683 $submission->timemarked) {
73963212 684 $output.= 'opener.document.getElementById("tt'.$submission->userid.
be86672d 685 '").innerHTML="'.userdate($submission->timemarked)."\";\n";
686 }
45fa3412 687
be86672d 688 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
73963212 689 $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
9bf660b3 690 $buttontext = get_string('update');
45fa3412 691 $button = link_to_popup_window ('/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;userid='.$submission->userid.'&amp;mode=single'.'&amp;offset='.(optional_param('offset', '', PARAM_INT)-1),
9bf660b3 692 'grade'.$submission->userid, $buttontext, 450, 700, $buttontext, 'none', true, 'button'.$submission->userid);
3a935caf 693 $output.= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
45fa3412 694 }
f1af7aaa 695
5978010d 696 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid);
697
698 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) {
699 $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid.
700 '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n";
701 }
702
703 if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) {
fcac8e51 704
705 if (!empty($grading_info->outcomes)) {
706 foreach($grading_info->outcomes as $n=>$outcome) {
5978010d 707 if ($outcome->grades[$submission->userid]->locked) {
cc03871b 708 continue;
709 }
710
711 if ($quickgrade){
712 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
fcac8e51 713 '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n";
714
cc03871b 715 } else {
fcac8e51 716 $options = make_grades_menu(-$outcome->scaleid);
cc03871b 717 $options[0] = get_string('nooutcome', 'grades');
fcac8e51 718 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n";
cc03871b 719 }
720
721 }
722 }
723 }
724
73963212 725 $output .= "\n-->\n</script>";
726 return $output;
be86672d 727 }
d699cd1e 728
7af1e882 729 /**
730 * Return a grade in user-friendly form, whether it's a scale or not
45fa3412 731 *
7af1e882 732 * @param $grade
733 * @return string User-friendly representation of grade
d59269cf 734 */
735 function display_grade($grade) {
736
c86aa2a4 737 static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!!
d59269cf 738
739 if ($this->assignment->grade >= 0) { // Normal number
082215e6 740 if ($grade == -1) {
741 return '-';
742 } else {
743 return $grade.' / '.$this->assignment->grade;
744 }
d59269cf 745
746 } else { // Scale
c86aa2a4 747 if (empty($scalegrades[$this->assignment->id])) {
d59269cf 748 if ($scale = get_record('scale', 'id', -($this->assignment->grade))) {
c86aa2a4 749 $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
d59269cf 750 } else {
751 return '-';
752 }
753 }
c86aa2a4 754 if (isset($scalegrades[$this->assignment->id][$grade])) {
755 return $scalegrades[$this->assignment->id][$grade];
0f7d4e5e 756 }
39e11905 757 return '-';
d59269cf 758 }
759 }
760
7af1e882 761 /**
b0f2597e 762 * Display a single submission, ready for grading on a popup window
7af1e882 763 *
ea6432fe 764 * This default method prints the teacher info and submissioncomment box at the top and
7af1e882 765 * the student info and submission at the bottom.
766 * This method also fetches the necessary data in order to be able to
767 * provide a "Next submission" button.
768 * Calls preprocess_submission() to give assignment type plug-ins a chance
769 * to process submissions before they are graded
770 * This method gets its arguments from the page parameters userid and offset
b0f2597e 771 */
73963212 772 function display_submission($extra_javascript = '') {
45fa3412 773
082215e6 774 global $CFG;
cc03871b 775 require_once($CFG->libdir.'/gradelib.php');
45fa3412 776
4fdabdc3 777 $userid = required_param('userid', PARAM_INT);
778 $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
d699cd1e 779
b0f2597e 780 if (!$user = get_record('user', 'id', $userid)) {
781 error('No such user!');
782 }
d699cd1e 783
39e11905 784 if (!$submission = $this->get_submission($user->id)) {
785 $submission = $this->prepare_new_submission($userid);
b0f2597e 786 }
b0f2597e 787 if ($submission->timemodified > $submission->timemarked) {
788 $subtype = 'assignmentnew';
789 } else {
790 $subtype = 'assignmentold';
791 }
d699cd1e 792
5978010d 793 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
794 $disabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
795
0cfafdc7 796 /// construct SQL, using current offset to find the data of the next student
9bf660b3 797 $course = $this->course;
798 $assignment = $this->assignment;
799 $cm = $this->cm;
16fc2088 800 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
d699cd1e 801
7bddd4b7 802 /// Get all ppl that can submit assignments
0cfafdc7 803
ba3dc7b4 804 $currentgroup = groups_get_activity_group($cm);
0cfafdc7 805
7bddd4b7 806 $users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id, u.id', '', '', '', $currentgroup, '', false);
5b48244f 807
f5ae7c49 808 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
45fa3412 809 s.id AS submissionid, s.grade, s.submissioncomment,
c9265600 810 s.timemodified, s.timemarked,
811 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
9bf660b3 812 $sql = 'FROM '.$CFG->prefix.'user u '.
45fa3412 813 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid
9ad5c91f 814 AND s.assignment = '.$this->assignment->id.' '.
082215e6 815 'WHERE u.id IN ('.implode(',', array_keys($users)).') ';
45fa3412 816
02828119 817 require_once($CFG->libdir.'/tablelib.php');
0cfafdc7 818
819 if ($sort = flexible_table::get_sql_sort('mod-assignment-submissions')) {
02828119 820 $sort = 'ORDER BY '.$sort.' ';
821 }
822
082215e6 823 $nextid = 0;
422770d8 824 if (($auser = get_records_sql($select.$sql.$sort, $offset+1, 1)) !== false) {
70ad6fdb 825 $nextuser = array_shift($auser);
9ad5c91f 826 /// Calculate user status
45fa3412 827 $nextuser->status = ($nextuser->timemarked > 0) && ($nextuser->timemarked >= $nextuser->timemodified);
70ad6fdb 828 $nextid = $nextuser->id;
9bf660b3 829 }
81532b92 830
9bf660b3 831 print_header(get_string('feedback', 'assignment').':'.fullname($user, true).':'.format_string($this->assignment->name));
d699cd1e 832
73963212 833 /// Print any extra javascript needed for saveandnext
834 echo $extra_javascript;
835
9bf660b3 836 ///SOme javascript to help with setting up >.>
45fa3412 837
c9977d05 838 echo '<script type="text/javascript">'."\n";
9bf660b3 839 echo 'function setNext(){'."\n";
16fc2088 840 echo 'document.getElementById(\'submitform\').mode.value=\'next\';'."\n";
841 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
9bf660b3 842 echo '}'."\n";
45fa3412 843
9bf660b3 844 echo 'function saveNext(){'."\n";
16fc2088 845 echo 'document.getElementById(\'submitform\').mode.value=\'saveandnext\';'."\n";
846 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
847 echo 'document.getElementById(\'submitform\').saveuserid.value="'.$userid.'";'."\n";
848 echo 'document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex;'."\n";
9bf660b3 849 echo '}'."\n";
45fa3412 850
9bf660b3 851 echo '</script>'."\n";
52436fe1 852 echo '<table cellspacing="0" class="feedback '.$subtype.'" >';
d699cd1e 853
9bf660b3 854 ///Start of teacher info row
c69cb506 855
b0f2597e 856 echo '<tr>';
141a922c 857 echo '<td class="picture teacher">';
b0f2597e 858 if ($submission->teacher) {
859 $teacher = get_record('user', 'id', $submission->teacher);
860 } else {
861 global $USER;
862 $teacher = $USER;
863 }
864 print_user_picture($teacher->id, $this->course->id, $teacher->picture);
865 echo '</td>';
866 echo '<td class="content">';
b7dc2256 867 echo '<form id="submitform" action="submissions.php" method="post">';
39bb937a 868 echo '<div>'; // xhtml compatibility - invisiblefieldset was breaking layout here
16fc2088 869 echo '<input type="hidden" name="offset" value="'.($offset+1).'" />';
c9977d05 870 echo '<input type="hidden" name="userid" value="'.$userid.'" />';
871 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
872 echo '<input type="hidden" name="mode" value="grade" />';
873 echo '<input type="hidden" name="menuindex" value="0" />';//selected menu index
45fa3412 874
9bf660b3 875 //new hidden field, initialized to -1.
c9977d05 876 echo '<input type="hidden" name="saveuserid" value="-1" />';
1a35ee51 877
52436fe1 878 if ($submission->timemarked) {
879 echo '<div class="from">';
880 echo '<div class="fullname">'.fullname($teacher, true).'</div>';
881 echo '<div class="time">'.userdate($submission->timemarked).'</div>';
882 echo '</div>';
883 }
1a35ee51 884 echo '<div class="grade"><label for="menugrade">'.get_string('grade').'</label> ';
5978010d 885 choose_from_menu(make_grades_menu($this->assignment->grade), 'grade', $submission->grade, get_string('nograde'), '', -1, false, $disabled);
52436fe1 886 echo '</div>';
3a5ae660 887
5978010d 888 echo '<div class="clearer"></div>';
889 echo '<div class="finalgrade">'.get_string('finalgrade', 'grades').': '.$grading_info->items[0]->grades[$userid]->str_grade.'</div>';
38f4c3fc 890 echo '<div class="clearer"></div>';
891
fcac8e51 892 if (!empty($CFG->enableoutcomes)) {
fcac8e51 893 foreach($grading_info->outcomes as $n=>$outcome) {
894 echo '<div class="outcome"><label for="menuoutcome_'.$n.'">'.$outcome->name.'</label> ';
895 $options = make_grades_menu(-$outcome->scaleid);
896 if ($outcome->grades[$submission->userid]->locked) {
cc03871b 897 $options[0] = get_string('nooutcome', 'grades');
fcac8e51 898 echo $options[$outcome->grades[$submission->userid]->grade];
3a5ae660 899 } else {
fcac8e51 900 choose_from_menu($options, 'outcome_'.$n.'['.$userid.']', $outcome->grades[$submission->userid]->grade, get_string('nooutcome', 'grades'), '', 0, false, false, 0, 'menuoutcome_'.$n);
3a5ae660 901 }
cc03871b 902 echo '</div>';
38f4c3fc 903 echo '<div class="clearer"></div>';
3a5ae660 904 }
3a5ae660 905 }
906
b0f2597e 907
01e2fdfe 908 $this->preprocess_submission($submission);
909
5978010d 910 if ($disabled) {
911 echo '<div class="disabledfeedback">'.$grading_info->items[0]->grades[$userid]->str_feedback.'</div>';
ff8f7015 912
ff8f7015 913 } else {
5978010d 914 print_textarea($this->usehtmleditor, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment, $this->course->id);
915 if ($this->usehtmleditor) {
916 echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />';
917 } else {
918 echo '<div class="format">';
919 choose_from_menu(format_text_menu(), "format", $submission->format, "");
920 helpbutton("textformat", get_string("helpformatting"));
921 echo '</div>';
922 }
ff8f7015 923 }
b0f2597e 924
9bf660b3 925 ///Print Buttons in Single View
141a922c 926 echo '<div class="buttons">';
16fc2088 927 echo '<input type="submit" name="submit" value="'.get_string('savechanges').'" onclick = "document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex" />';
b0f2597e 928 echo '<input type="submit" name="cancel" value="'.get_string('cancel').'" />';
9bf660b3 929 //if there are more to be graded.
082215e6 930 if ($nextid) {
5b48244f 931 echo '<input type="submit" name="saveandnext" value="'.get_string('saveandnext').'" onclick="saveNext()" />';
932 echo '<input type="submit" name="next" value="'.get_string('next').'" onclick="setNext();" />';
9bf660b3 933 }
73097f07 934 echo '</div>';
39bb937a 935 echo '</div></form>';
55b4d096 936
937 $customfeedback = $this->custom_feedbackform($submission, true);
938 if (!empty($customfeedback)) {
45fa3412 939 echo $customfeedback;
55b4d096 940 }
941
73097f07 942 echo '</td></tr>';
45fa3412 943
9bf660b3 944 ///End of teacher info row, Start of student info row
945 echo '<tr>';
141a922c 946 echo '<td class="picture user">';
9bf660b3 947 print_user_picture($user->id, $this->course->id, $user->picture);
948 echo '</td>';
949 echo '<td class="topic">';
950 echo '<div class="from">';
951 echo '<div class="fullname">'.fullname($user, true).'</div>';
952 if ($submission->timemodified) {
953 echo '<div class="time">'.userdate($submission->timemodified).
954 $this->display_lateness($submission->timemodified).'</div>';
955 }
956 echo '</div>';
957 $this->print_user_files($user->id);
958 echo '</td>';
959 echo '</tr>';
45fa3412 960
9bf660b3 961 ///End of student info row
45fa3412 962
73097f07 963 echo '</table>';
964
5978010d 965 if (!$disabled and $this->usehtmleditor) {
73097f07 966 use_html_editor();
967 }
968
b0f2597e 969 print_footer('none');
d699cd1e 970 }
971
7af1e882 972 /**
01e2fdfe 973 * Preprocess submission before grading
7af1e882 974 *
975 * Called by display_submission()
976 * The default type does nothing here.
977 * @param $submission object The submission object
01e2fdfe 978 */
979 function preprocess_submission(&$submission) {
980 }
d699cd1e 981
7af1e882 982 /**
b0f2597e 983 * Display all the submissions ready for grading
984 */
fb81abe1 985 function display_submissions($message='') {
9bf660b3 986 global $CFG, $db, $USER;
cc03871b 987 require_once($CFG->libdir.'/gradelib.php');
3446205d 988
9bf660b3 989 /* first we check to see if the form has just been submitted
990 * to request user_preference updates
991 */
45fa3412 992
9bf660b3 993 if (isset($_POST['updatepref'])){
16907e53 994 $perpage = optional_param('perpage', 10, PARAM_INT);
9bf660b3 995 $perpage = ($perpage <= 0) ? 10 : $perpage ;
996 set_user_preference('assignment_perpage', $perpage);
16907e53 997 set_user_preference('assignment_quickgrade', optional_param('quickgrade',0, PARAM_BOOL));
9bf660b3 998 }
1b5910c4 999
45fa3412 1000 /* next we get perpage and quickgrade (allow quick grade) params
9bf660b3 1001 * from database
1002 */
1003 $perpage = get_user_preferences('assignment_perpage', 10);
34e67f76 1004
fcac8e51 1005 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
1006
1007 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
45fa3412 1008
fcac8e51 1009 if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
e46f4d11 1010 $uses_outcomes = true;
1011 } else {
1012 $uses_outcomes = false;
1013 }
1014
9bf660b3 1015 $teacherattempts = true; /// Temporary measure
16907e53 1016 $page = optional_param('page', 0, PARAM_INT);
b0f2597e 1017 $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
d0ac6bc2 1018
b0f2597e 1019 /// Some shortcuts to make the code read better
45fa3412 1020
b0f2597e 1021 $course = $this->course;
1022 $assignment = $this->assignment;
1023 $cm = $this->cm;
45fa3412 1024
9bf660b3 1025 $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
91719320 1026
b0f2597e 1027 add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment->id, $this->assignment->id, $this->cm->id);
206ab184 1028
38e179a4 1029 $navigation = build_navigation($this->strsubmissions, $this->cm);
206ab184 1030 print_header_simple(format_string($this->assignment->name,true), "", $navigation,
1031 '', '', true, update_module_button($cm->id, $course->id, $this->strassignment), navmenu($course, $cm));
7bddd4b7 1032
fb81abe1 1033 if (!empty($message)) {
1034 echo $message; // display messages here if any
1035 }
1036
2d7617c6 1037 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
7bddd4b7 1038
1039 /// find out current groups mode
ba3dc7b4 1040 $groupmode = groups_get_activity_groupmode($cm);
1041 $currentgroup = groups_get_activity_group($cm, true);
1042 groups_print_activity_menu($cm, 'submissions.php?id=' . $this->cm->id);
7bddd4b7 1043
1044 /// Get all ppl that are allowed to submit assignments
1045 $users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', $currentgroup, '', false);
ba3dc7b4 1046 $users = array_keys($users);
1047
1048 if (!empty($CFG->enablegroupings) && !empty($cm->groupingid)) {
1049 $groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id');
1050 $users = array_intersect($users, array_keys($groupingusers));
5978010d 1051
ba3dc7b4 1052 }
91719320 1053
5978010d 1054 $tablecolumns = array('picture', 'fullname', 'grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade');
e46f4d11 1055 if ($uses_outcomes) {
5978010d 1056 $tablecolumns[] = 'outcome'; // no sorting based on outcomes column
fbaa56b2 1057 }
1058
206ab184 1059 $tableheaders = array('',
1060 get_string('fullname'),
1061 get_string('grade'),
1062 get_string('comment', 'assignment'),
1063 get_string('lastmodified').' ('.$course->student.')',
1064 get_string('lastmodified').' ('.$course->teacher.')',
5978010d 1065 get_string('status'),
1066 get_string('finalgrade', 'grades'));
e46f4d11 1067 if ($uses_outcomes) {
5978010d 1068 $tableheaders[] = get_string('outcome', 'grades');
fbaa56b2 1069 }
91719320 1070
b0f2597e 1071 require_once($CFG->libdir.'/tablelib.php');
1072 $table = new flexible_table('mod-assignment-submissions');
45fa3412 1073
b0f2597e 1074 $table->define_columns($tablecolumns);
1075 $table->define_headers($tableheaders);
fa22fd5f 1076 $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
45fa3412 1077
246444b9 1078 $table->sortable(true, 'lastname');//sorted by lastname by default
b0f2597e 1079 $table->collapsible(true);
1080 $table->initialbars(true);
45fa3412 1081
b0f2597e 1082 $table->column_suppress('picture');
1083 $table->column_suppress('fullname');
45fa3412 1084
b0f2597e 1085 $table->column_class('picture', 'picture');
9437c854 1086 $table->column_class('fullname', 'fullname');
1087 $table->column_class('grade', 'grade');
ea6432fe 1088 $table->column_class('submissioncomment', 'comment');
9437c854 1089 $table->column_class('timemodified', 'timemodified');
1090 $table->column_class('timemarked', 'timemarked');
1091 $table->column_class('status', 'status');
5978010d 1092 $table->column_class('finalgrade', 'finalgrade');
e46f4d11 1093 if ($uses_outcomes) {
5978010d 1094 $table->column_class('outcome', 'outcome');
fbaa56b2 1095 }
45fa3412 1096
b0f2597e 1097 $table->set_attribute('cellspacing', '0');
1098 $table->set_attribute('id', 'attempts');
9437c854 1099 $table->set_attribute('class', 'submissions');
b0f2597e 1100 $table->set_attribute('width', '90%');
d9cb14b8 1101 //$table->set_attribute('align', 'center');
45fa3412 1102
5978010d 1103 $table->no_sorting('finalgrade');
1104 $table->no_sorting('outcome');
1105
b0f2597e 1106 // Start working -- this is necessary as soon as the niceties are over
1107 $table->setup();
1108
b0f2597e 1109 /// Check to see if groups are being used in this assignment
05855091 1110
b0f2597e 1111 if (!$teacherattempts) {
1112 $teachers = get_course_teachers($course->id);
1113 if (!empty($teachers)) {
1114 $keys = array_keys($teachers);
1115 }
1116 foreach ($keys as $key) {
1117 unset($users[$key]);
1118 }
1119 }
45fa3412 1120
b0f2597e 1121 if (empty($users)) {
c8dbfa5c 1122 print_heading(get_string('noattempts','assignment'));
b0f2597e 1123 return true;
1124 }
0f1a97c2 1125
b0f2597e 1126 /// Construct the SQL
0f1a97c2 1127
b0f2597e 1128 if ($where = $table->get_sql_where()) {
b0f2597e 1129 $where .= ' AND ';
1130 }
0f1a97c2 1131
b0f2597e 1132 if ($sort = $table->get_sql_sort()) {
86f65395 1133 $sort = ' ORDER BY '.$sort;
b0f2597e 1134 }
9fa49e22 1135
45fa3412 1136 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
1137 s.id AS submissionid, s.grade, s.submissioncomment,
c9265600 1138 s.timemodified, s.timemarked,
1139 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
b0f2597e 1140 $sql = 'FROM '.$CFG->prefix.'user u '.
45fa3412 1141 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid
9ad5c91f 1142 AND s.assignment = '.$this->assignment->id.' '.
ba3dc7b4 1143 'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
45fa3412 1144
c5d36203 1145 $table->pagesize($perpage, count($users));
45fa3412 1146
9bf660b3 1147 ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
1148 $offset = $page * $perpage;
45fa3412 1149
b0f2597e 1150 $strupdate = get_string('update');
9437c854 1151 $strgrade = get_string('grade');
b0f2597e 1152 $grademenu = make_grades_menu($this->assignment->grade);
1153
422770d8 1154 if (($ausers = get_records_sql($select.$sql.$sort, $table->get_page_start(), $table->get_page_size())) !== false) {
fcac8e51 1155 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
d59269cf 1156 foreach ($ausers as $auser) {
5978010d 1157 $final_grade = $grading_info->items[0]->grades[$auser->id];
9ad5c91f 1158 /// Calculate user status
2ff44114 1159 $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
d59269cf 1160 $picture = print_user_picture($auser->id, $course->id, $auser->picture, false, true);
45fa3412 1161
39e11905 1162 if (empty($auser->submissionid)) {
1163 $auser->grade = -1; //no submission yet
9bf660b3 1164 }
45fa3412 1165
d59269cf 1166 if (!empty($auser->submissionid)) {
9bf660b3 1167 ///Prints student answer and student modified date
1168 ///attach file or print link to student answer, depending on the type of the assignment.
45fa3412 1169 ///Refer to print_student_answer in inherited classes.
1170 if ($auser->timemodified > 0) {
206ab184 1171 $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id)
1172 . userdate($auser->timemodified).'</div>';
d59269cf 1173 } else {
9437c854 1174 $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
d59269cf 1175 }
9bf660b3 1176 ///Print grade, dropdown or text
d59269cf 1177 if ($auser->timemarked > 0) {
1178 $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
45fa3412 1179
5978010d 1180 if ($final_grade->locked or $final_grade->overridden) {
1181 $grade = '<div id="g'.$auser->id.'">'.$final_grade->str_grade.'</div>';
1182 } else if ($quickgrade) {
206ab184 1183 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1184 'menu['.$auser->id.']', $auser->grade,
1185 get_string('nograde'),'',-1,true,false,$tabindex++);
1186 $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
9bf660b3 1187 } else {
1188 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1189 }
1190
b0f2597e 1191 } else {
9437c854 1192 $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
5978010d 1193 if ($final_grade->locked or $final_grade->overridden) {
1194 $grade = '<div id="g'.$auser->id.'">'.$final_grade->str_grade.'</div>';
1195 } else if ($quickgrade) {
206ab184 1196 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1197 'menu['.$auser->id.']', $auser->grade,
1198 get_string('nograde'),'',-1,true,false,$tabindex++);
1199 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
9bf660b3 1200 } else {
1201 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1202 }
1203 }
1204 ///Print Comment
5978010d 1205 if ($final_grade->locked or $final_grade->overridden) {
1206 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
1207
1208 } else if ($quickgrade) {
206ab184 1209 $comment = '<div id="com'.$auser->id.'">'
1210 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1211 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
9bf660b3 1212 } else {
ea6432fe 1213 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
b0f2597e 1214 }
1215 } else {
9437c854 1216 $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1217 $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
9bf660b3 1218 $status = '<div id="st'.$auser->id.'">&nbsp;</div>';
206ab184 1219
5978010d 1220 if ($final_grade->locked or $final_grade->overridden) {
1221 $grade = '<div id="g'.$auser->id.'">'.$final_grade->str_grade.'</div>';
1222 } else if ($quickgrade) { // allow editing
206ab184 1223 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1224 'menu['.$auser->id.']', $auser->grade,
1225 get_string('nograde'),'',-1,true,false,$tabindex++);
1226 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
9bf660b3 1227 } else {
39e11905 1228 $grade = '<div id="g'.$auser->id.'">-</div>';
9bf660b3 1229 }
206ab184 1230
5978010d 1231 if ($final_grade->locked or $final_grade->overridden) {
1232 $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
1233 } else if ($quickgrade) {
206ab184 1234 $comment = '<div id="com'.$auser->id.'">'
1235 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1236 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
9bf660b3 1237 } else {
1238 $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
1239 }
b0f2597e 1240 }
9fa49e22 1241
9ad5c91f 1242 if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
0f7d4e5e 1243 $auser->status = 0;
9ad5c91f 1244 } else {
1245 $auser->status = 1;
0f7d4e5e 1246 }
1247
9437c854 1248 $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
45fa3412 1249
fcac8e51 1250 ///No more buttons, we use popups ;-).
1251 $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
1252 . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;offset='.$offset++;
1253 $button = link_to_popup_window ($popup_url, 'grade'.$auser->id, $buttontext, 600, 780,
1254 $buttontext, 'none', true, 'button'.$auser->id);
206ab184 1255
fcac8e51 1256 $status = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
cc03871b 1257
5978010d 1258 $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
1259
cc03871b 1260 $outcomes = '';
206ab184 1261
fcac8e51 1262 if ($uses_outcomes) {
206ab184 1263
fcac8e51 1264 foreach($grading_info->outcomes as $n=>$outcome) {
1265 $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
1266 $options = make_grades_menu(-$outcome->scaleid);
206ab184 1267
fcac8e51 1268 if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
cc03871b 1269 $options[0] = get_string('nooutcome', 'grades');
fcac8e51 1270 $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
cc03871b 1271 } else {
e46f4d11 1272 $outcomes .= ' ';
206ab184 1273 $outcomes .= choose_from_menu($options, 'outcome_'.$n.'['.$auser->id.']',
fcac8e51 1274 $outcome->grades[$auser->id]->grade, get_string('nooutcome', 'grades'), '', 0, true, false, 0, 'outcome_'.$n.'_'.$auser->id);
cc03871b 1275 }
206ab184 1276 $outcomes .= '</div>';
cc03871b 1277 }
1278 }
1279
1280
5978010d 1281 $row = array($picture, fullname($auser), $grade, $comment, $studentmodified, $teachermodified, $status, $finalgrade);
e46f4d11 1282 if ($uses_outcomes) {
fbaa56b2 1283 $row[] = $outcomes;
1284 }
1285
d59269cf 1286 $table->add_data($row);
1287 }
b0f2597e 1288 }
45fa3412 1289
082215e6 1290 /// Print quickgrade form around the table
1291 if ($quickgrade){
b7dc2256 1292 echo '<form action="submissions.php" id="fastg" method="post">';
a1b5dd2b 1293 echo '<div>';
820aff13 1294 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
1295 echo '<input type="hidden" name="mode" value="fastgrade" />';
1296 echo '<input type="hidden" name="page" value="'.$page.'" />';
a1b5dd2b 1297 echo '</div>';
fb81abe1 1298 //echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
082215e6 1299 }
1300
1301 $table->print_html(); /// Print the whole table
1302
9bf660b3 1303 if ($quickgrade){
d9cb14b8 1304 echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
082215e6 1305 echo '</form>';
9bf660b3 1306 }
082215e6 1307 /// End of fast grading form
45fa3412 1308
082215e6 1309 /// Mini form for setting user preference
9bf660b3 1310 echo '<br />';
b7dc2256 1311 echo '<form id="options" action="submissions.php?id='.$this->cm->id.'" method="post">';
ee8652f3 1312 echo '<div>';
c9977d05 1313 echo '<input type="hidden" id="updatepref" name="updatepref" value="1" />';
ee8652f3 1314 echo '<table id="optiontable" align="right">';
9bf660b3 1315 echo '<tr align="right"><td>';
1316 echo '<label for="perpage">'.get_string('pagesize','assignment').'</label>';
1317 echo ':</td>';
ee8652f3 1318 echo '<td>';
9bf660b3 1319 echo '<input type="text" id="perpage" name="perpage" size="1" value="'.$perpage.'" />';
1320 helpbutton('pagesize', get_string('pagesize','assignment'), 'assignment');
1321 echo '</td></tr>';
fcac8e51 1322 echo '<tr align="right">';
1323 echo '<td>';
1324 print_string('quickgrade','assignment');
1325 echo ':</td>';
ee8652f3 1326 echo '<td>';
fcac8e51 1327 if ($quickgrade){
1328 echo '<input type="checkbox" name="quickgrade" value="1" checked="checked" />';
1329 } else {
1330 echo '<input type="checkbox" name="quickgrade" value="1" />';
9bf660b3 1331 }
fcac8e51 1332 helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment').'</p></div>';
1333 echo '</td></tr>';
9bf660b3 1334 echo '<tr>';
1335 echo '<td colspan="2" align="right">';
1336 echo '<input type="submit" value="'.get_string('savepreferences').'" />';
1337 echo '</td></tr></table>';
ee8652f3 1338 echo '</div>';
9bf660b3 1339 echo '</form>';
1340 ///End of mini form
b0f2597e 1341 print_footer($this->course);
8e340cb0 1342 }
d699cd1e 1343
7af1e882 1344 /**
1345 * Process teacher feedback submission
1346 *
1347 * This is called by submissions() when a grading even has taken place.
1348 * It gets its data from the submitted form.
1349 * @return object The updated submission object
b0f2597e 1350 */
1351 function process_feedback() {
5978010d 1352 global $CFG, $USER;
1353 require_once($CFG->libdir.'/gradelib.php');
d699cd1e 1354
9894b824 1355 if (!$feedback = data_submitted()) { // No incoming data?
b0f2597e 1356 return false;
d699cd1e 1357 }
b7b42874 1358
9bf660b3 1359 ///For save and next, we need to know the userid to save, and the userid to go
1360 ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
1361 ///as the userid to store
1362 if ((int)$feedback->saveuserid !== -1){
1363 $feedback->userid = $feedback->saveuserid;
1364 }
1365
b0f2597e 1366 if (!empty($feedback->cancel)) { // User hit cancel button
1367 return false;
1368 }
d699cd1e 1369
5978010d 1370 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
1371
cc03871b 1372 // store outcomes if needed
1373 $this->process_outcomes($feedback->userid);
1374
7af1e882 1375 $submission = $this->get_submission($feedback->userid, true); // Get or make one
d699cd1e 1376
5978010d 1377 if (!$grading_info->items[0]->grades[$feedback->userid]->locked and
1378 !$grading_info->items[0]->grades[$feedback->userid]->overridden) {
d699cd1e 1379
5978010d 1380 $submission->grade = $feedback->grade;
1381 $submission->submissioncomment = $feedback->submissioncomment;
1382 $submission->format = $feedback->format;
1383 $submission->teacher = $USER->id;
1384 $submission->mailed = 0; // Make sure mail goes out (again, even)
1385 $submission->timemarked = time();
d4156e80 1386
5978010d 1387 unset($submission->data1); // Don't need to update this.
1388 unset($submission->data2); // Don't need to update this.
d699cd1e 1389
5978010d 1390 if (empty($submission->timemodified)) { // eg for offline assignments
1391 // $submission->timemodified = time();
1392 }
d699cd1e 1393
5978010d 1394 if (! update_record('assignment_submissions', $submission)) {
1395 return false;
1396 }
7bddd4b7 1397
5978010d 1398 // triger grade event
1399 $this->update_grade($submission);
1400
1401 add_to_log($this->course->id, 'assignment', 'update grades',
1402 'submissions.php?id='.$this->assignment->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
1403 }
45fa3412 1404
7af1e882 1405 return $submission;
d699cd1e 1406
d699cd1e 1407 }
d699cd1e 1408
cc03871b 1409 function process_outcomes($userid) {
1410 global $CFG, $USER;
fbaa56b2 1411
1412 if (empty($CFG->enableoutcomes)) {
1413 return;
1414 }
1415
cc03871b 1416 require_once($CFG->libdir.'/gradelib.php');
1417
1418 if (!$formdata = data_submitted()) {
1419 return;
1420 }
1421
1422 $data = array();
fcac8e51 1423 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
1424
1425 if (!empty($grading_info->outcomes)) {
1426 foreach($grading_info->outcomes as $n=>$old) {
cc03871b 1427 $name = 'outcome_'.$n;
fcac8e51 1428 if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
cc03871b 1429 $data[$n] = $formdata->{$name}[$userid];
1430 }
1431 }
1432 }
1433 if (count($data) > 0) {
1434 grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
1435 }
1436
1437 }
1438
7af1e882 1439 /**
1440 * Load the submission object for a particular user
1441 *
1442 * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
1443 * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
1444 * @return object The submission
1445 */
f77cfb73 1446 function get_submission($userid=0, $createnew=false) {
1447 global $USER;
1448
1449 if (empty($userid)) {
1450 $userid = $USER->id;
1451 }
1452
b0f2597e 1453 $submission = get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid);
d699cd1e 1454
b0f2597e 1455 if ($submission || !$createnew) {
1456 return $submission;
1457 }
39e11905 1458 $newsubmission = $this->prepare_new_submission($userid);
b0f2597e 1459 if (!insert_record("assignment_submissions", $newsubmission)) {
1460 error("Could not insert a new empty submission");
1461 }
d699cd1e 1462
b0f2597e 1463 return get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid);
1464 }
d699cd1e 1465
7af1e882 1466 /**
1467 * Instantiates a new submission object for a given user
1468 *
1469 * Sets the assignment, userid and times, everything else is set to default values.
1470 * @param $userid int The userid for which we want a submission object
1471 * @return object The submission
1472 */
39e11905 1473 function prepare_new_submission($userid) {
45fa3412 1474 $submission = new Object;
39e11905 1475 $submission->assignment = $this->assignment->id;
1476 $submission->userid = $userid;
45fa3412 1477 //$submission->timecreated = time();
16fc2088 1478 $submission->timecreated = '';
1479 // teachers should not be modifying modified date, except offline assignments
39e11905 1480 $submission->timemodified = $submission->timecreated;
1481 $submission->numfiles = 0;
1482 $submission->data1 = '';
1483 $submission->data2 = '';
1484 $submission->grade = -1;
ea6432fe 1485 $submission->submissioncomment = '';
39e11905 1486 $submission->format = 0;
1487 $submission->teacher = 0;
1488 $submission->timemarked = 0;
1489 $submission->mailed = 0;
1490 return $submission;
1491 }
1492
7af1e882 1493 /**
1494 * Return all assignment submissions by ENROLLED students (even empty)
1495 *
1496 * @param $sort string optional field names for the ORDER BY in the sql query
1497 * @param $dir string optional specifying the sort direction, defaults to DESC
1498 * @return array The submission objects indexed by id
1499 */
b0f2597e 1500 function get_submissions($sort='', $dir='DESC') {
7af1e882 1501 return assignment_get_all_submissions($this->assignment, $sort, $dir);
b0f2597e 1502 }
1503
7af1e882 1504 /**
1505 * Counts all real assignment submissions by ENROLLED students (not empty ones)
1506 *
1507 * @param $groupid int optional If nonzero then count is restricted to this group
1508 * @return int The number of submissions
1509 */
b0f2597e 1510 function count_real_submissions($groupid=0) {
7af1e882 1511 return assignment_count_real_submissions($this->assignment, $groupid);
d59269cf 1512 }
d699cd1e 1513
7af1e882 1514 /**
1515 * Alerts teachers by email of new or changed assignments that need grading
1516 *
1517 * First checks whether the option to email teachers is set for this assignment.
1518 * Sends an email to ALL teachers in the course (or in the group if using separate groups).
1519 * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
1520 * @param $submission object The submission that has changed
1521 */
73097f07 1522 function email_teachers($submission) {
73097f07 1523 global $CFG;
1524
d8199f1d 1525 if (empty($this->assignment->emailteachers)) { // No need to do anything
73097f07 1526 return;
1527 }
1528
1529 $user = get_record('user', 'id', $submission->userid);
1530
413efd22 1531 if ($teachers = $this->get_graders($user)) {
73097f07 1532
1533 $strassignments = get_string('modulenameplural', 'assignment');
1534 $strassignment = get_string('modulename', 'assignment');
1535 $strsubmitted = get_string('submitted', 'assignment');
1536
1537 foreach ($teachers as $teacher) {
98be6ed8 1538 $info = new object();
413efd22 1539 $info->username = fullname($user, true);
d8199f1d 1540 $info->assignment = format_string($this->assignment->name,true);
1541 $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
1542
1543 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
1544 $posttext = $this->email_teachers_text($info);
1545 $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
73097f07 1546
1547 @email_to_user($teacher, $user, $postsubject, $posttext, $posthtml); // If it fails, oh well, too bad.
1548 }
1549 }
1550 }
1551
413efd22 1552 /**
1553 * Returns a list of teachers that should be grading given submission
1554 */
1555 function get_graders($user) {
1556 //potential graders
7bddd4b7 1557 $potgraders = get_users_by_capability($this->context, 'mod/assignment:grade', '', '', '', '', '', '', false, false);
1558
413efd22 1559 $graders = array();
ba3dc7b4 1560 if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) { // Separate groups are being used
2c386f82 1561 if ($groups = groups_get_all_groups($this->course->id, $user->id)) { // Try to find all groups
413efd22 1562 foreach ($groups as $group) {
1563 foreach ($potgraders as $t) {
1564 if ($t->id == $user->id) {
1565 continue; // do not send self
1566 }
1567 if (groups_is_member($group->id, $t->id)) {
1568 $graders[$t->id] = $t;
1569 }
1570 }
1571 }
1572 } else {
1573 // user not in group, try to find graders without group
1574 foreach ($potgraders as $t) {
1575 if ($t->id == $user->id) {
1576 continue; // do not send self
1577 }
2c386f82 1578 if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
413efd22 1579 $graders[$t->id] = $t;
1580 }
1581 }
1582 }
1583 } else {
1584 foreach ($potgraders as $t) {
1585 if ($t->id == $user->id) {
1586 continue; // do not send self
1587 }
1588 $graders[$t->id] = $t;
1589 }
1590 }
1591 return $graders;
1592 }
1593
7af1e882 1594 /**
1595 * Creates the text content for emails to teachers
1596 *
1597 * @param $info object The info used by the 'emailteachermail' language string
1598 * @return string
1599 */
d8199f1d 1600 function email_teachers_text($info) {
413efd22 1601 $posttext = format_string($this->course->shortname).' -> '.$this->strassignments.' -> '.
1602 format_string($this->assignment->name)."\n";
d8199f1d 1603 $posttext .= '---------------------------------------------------------------------'."\n";
1604 $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
73963212 1605 $posttext .= "\n---------------------------------------------------------------------\n";
d8199f1d 1606 return $posttext;
1607 }
1608
7af1e882 1609 /**
1610 * Creates the html content for emails to teachers
1611 *
1612 * @param $info object The info used by the 'emailteachermailhtml' language string
1613 * @return string
1614 */
d8199f1d 1615 function email_teachers_html($info) {
3554b5c2 1616 global $CFG;
d8199f1d 1617 $posthtml = '<p><font face="sans-serif">'.
413efd22 1618 '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname).'</a> ->'.
d8199f1d 1619 '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
413efd22 1620 '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name).'</a></font></p>';
d8199f1d 1621 $posthtml .= '<hr /><font face="sans-serif">';
1622 $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
1623 $posthtml .= '</font><hr />';
815b5ca6 1624 return $posthtml;
d8199f1d 1625 }
1626
7af1e882 1627 /**
1628 * Produces a list of links to the files uploaded by a user
1629 *
1630 * @param $userid int optional id of the user. If 0 then $USER->id is used.
1631 * @param $return boolean optional defaults to false. If true the list is returned rather than printed
1632 * @return string optional
1633 */
d8199f1d 1634 function print_user_files($userid=0, $return=false) {
d8199f1d 1635 global $CFG, $USER;
45fa3412 1636
d8199f1d 1637 if (!$userid) {
1638 if (!isloggedin()) {
1639 return '';
1640 }
1641 $userid = $USER->id;
1642 }
45fa3412 1643
70b2c772 1644 $filearea = $this->file_area_name($userid);
73097f07 1645
1646 $output = '';
45fa3412 1647
70b2c772 1648 if ($basedir = $this->file_area($userid)) {
73097f07 1649 if ($files = get_directory_list($basedir)) {
1ef048ae 1650 require_once($CFG->libdir.'/filelib.php');
73097f07 1651 foreach ($files as $key => $file) {
45fa3412 1652
73097f07 1653 $icon = mimeinfo('icon', $file);
45fa3412 1654
73097f07 1655 if ($CFG->slasharguments) {
d4156e80 1656 $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
73097f07 1657 } else {
d4156e80 1658 $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
73097f07 1659 }
45fa3412 1660
0d905d9f 1661 $output .= '<img align="middle" src="'.$CFG->pixpath.'/f/'.$icon.'" class="icon" alt="'.$icon.'" />'.
9bf660b3 1662 '<a href="'.$ffurl.'" >'.$file.'</a><br />';
73097f07 1663 }
1664 }
1665 }
1666
1667 $output = '<div class="files">'.$output.'</div>';
1668
1669 if ($return) {
1670 return $output;
1671 }
1672 echo $output;
1673 }
1674
7af1e882 1675 /**
1676 * Count the files uploaded by a given user
1677 *
1678 * @param $userid int The user id
1679 * @return int
1680 */
70b2c772 1681 function count_user_files($userid) {
1682 global $CFG;
1683
1684 $filearea = $this->file_area_name($userid);
1685
c853b39f 1686 if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) {
70b2c772 1687 if ($files = get_directory_list($basedir)) {
1688 return count($files);
1689 }
1690 }
1691 return 0;
1692 }
73097f07 1693
7af1e882 1694 /**
1695 * Creates a directory file name, suitable for make_upload_directory()
1696 *
1697 * @param $userid int The user id
1698 * @return string path to file area
1699 */
70b2c772 1700 function file_area_name($userid) {
73097f07 1701 global $CFG;
45fa3412 1702
70b2c772 1703 return $this->course->id.'/'.$CFG->moddata.'/assignment/'.$this->assignment->id.'/'.$userid;
73097f07 1704 }
7af1e882 1705
1706 /**
1707 * Makes an upload directory
1708 *
1709 * @param $userid int The user id
1710 * @return string path to file area.
1711 */
70b2c772 1712 function file_area($userid) {
1713 return make_upload_directory( $this->file_area_name($userid) );
73097f07 1714 }
1715
7af1e882 1716 /**
1717 * Returns true if the student is allowed to submit
1718 *
1719 * Checks that the assignment has started and, if the option to prevent late
1720 * submissions is set, also checks that the assignment has not yet closed.
1721 * @return boolean
1722 */
f77cfb73 1723 function isopen() {
1724 $time = time();
1e4343a0 1725 if ($this->assignment->preventlate && $this->assignment->timedue) {
f77cfb73 1726 return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
1727 } else {
1728 return ($this->assignment->timeavailable <= $time);
1729 }
1730 }
1731
7af1e882 1732 /**
1733 * Return an outline of the user's interaction with the assignment
1734 *
1735 * The default method prints the grade and timemodified
1736 * @param $user object
1737 * @return object with properties ->info and ->time
1738 */
73097f07 1739 function user_outline($user) {
1740 if ($submission = $this->get_submission($user->id)) {
39e11905 1741
98be6ed8 1742 $result = new object();
39e11905 1743 $result->info = get_string('grade').': '.$this->display_grade($submission->grade);
73097f07 1744 $result->time = $submission->timemodified;
1745 return $result;
1746 }
1747 return NULL;
1748 }
7af1e882 1749
1750 /**
1751 * Print complete information about the user's interaction with the assignment
1752 *
1753 * @param $user object
1754 */
73097f07 1755 function user_complete($user) {
1756 if ($submission = $this->get_submission($user->id)) {
70b2c772 1757 if ($basedir = $this->file_area($user->id)) {
73097f07 1758 if ($files = get_directory_list($basedir)) {
1759 $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
1760 foreach ($files as $file) {
1761 $countfiles .= "; $file";
1762 }
1763 }
1764 }
45fa3412 1765
73097f07 1766 print_simple_box_start();
1767 echo get_string("lastmodified").": ";
9bf660b3 1768 echo userdate($submission->timemodified);
1769 echo $this->display_lateness($submission->timemodified);
45fa3412 1770
70b2c772 1771 $this->print_user_files($user->id);
45fa3412 1772
73097f07 1773 echo '<br />';
45fa3412 1774
73097f07 1775 if (empty($submission->timemarked)) {
1776 print_string("notgradedyet", "assignment");
1777 } else {
1778 $this->view_feedback($submission);
1779 }
45fa3412 1780
73097f07 1781 print_simple_box_end();
45fa3412 1782
73097f07 1783 } else {
1784 print_string("notsubmittedyet", "assignment");
1785 }
1786 }
1787
7af1e882 1788 /**
1789 * Return a string indicating how late a submission is
1790 *
45fa3412 1791 * @param $timesubmitted int
7af1e882 1792 * @return string
1793 */
70b2c772 1794 function display_lateness($timesubmitted) {
76a60031 1795 return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
73097f07 1796 }
1797
55b4d096 1798 /**
1799 * Empty method stub for all delete actions.
1800 */
1801 function delete() {
1802 //nothing by default
1803 redirect('view.php?id='.$this->cm->id);
1804 }
1805
1806 /**
1807 * Empty custom feedback grading form.
1808 */
1809 function custom_feedbackform($submission, $return=false) {
1810 //nothing by default
1811 return '';
1812 }
73097f07 1813
09ba8e56 1814 /**
1815 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
1816 * for the course (see resource).
1817 *
206ab184 1818 * Given a course_module object, this function returns any "extra" information that may be needed
09ba8e56 1819 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
206ab184 1820 *
09ba8e56 1821 * @param $coursemodule object The coursemodule object (record).
1822 * @return object An object on information that the coures will know about (most noticeably, an icon).
206ab184 1823 *
09ba8e56 1824 */
1825 function get_coursemodule_info($coursemodule) {
1826 return false;
1827 }
1828
d014b69b 1829 /**
1830 * Plugin cron method - do not use $this here, create new assignment instances if needed.
1831 * @return void
1832 */
1833 function cron() {
1834 //no plugin cron by default - override if needed
1835 }
1836
b0f2597e 1837} ////// End of the assignment_base class
d699cd1e 1838
18b8fbfa 1839
04eba58f 1840
b0f2597e 1841/// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
1842
7af1e882 1843/**
1844 * Deletes an assignment instance
1845 *
1846 * This is done by calling the delete_instance() method of the assignment type class
1847 */
b0f2597e 1848function assignment_delete_instance($id){
26b90e70 1849 global $CFG;
1850
b0f2597e 1851 if (! $assignment = get_record('assignment', 'id', $id)) {
1852 return false;
26b90e70 1853 }
1854
1fc87774 1855 // fall back to base class if plugin missing
1856 $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
1857 if (file_exists($classfile)) {
1858 require_once($classfile);
1859 $assignmentclass = "assignment_$assignment->assignmenttype";
1860
1861 } else {
1862 debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
1863 $assignmentclass = "assignment_base";
1864 }
1865
b0f2597e 1866 $ass = new $assignmentclass();
1867 return $ass->delete_instance($assignment);
1868}
f466c9ed 1869
ac21ad39 1870
7af1e882 1871/**
1872 * Updates an assignment instance
1873 *
1874 * This is done by calling the update_instance() method of the assignment type class
1875 */
b0f2597e 1876function assignment_update_instance($assignment){
1877 global $CFG;
26b90e70 1878
200c19fb 1879 $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
1880
b0f2597e 1881 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1882 $assignmentclass = "assignment_$assignment->assignmenttype";
1883 $ass = new $assignmentclass();
1884 return $ass->update_instance($assignment);
45fa3412 1885}
26b90e70 1886
26b90e70 1887
7af1e882 1888/**
1889 * Adds an assignment instance
1890 *
1891 * This is done by calling the add_instance() method of the assignment type class
1892 */
b0f2597e 1893function assignment_add_instance($assignment) {
1894 global $CFG;
f466c9ed 1895
200c19fb 1896 $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
1897
b0f2597e 1898 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1899 $assignmentclass = "assignment_$assignment->assignmenttype";
1900 $ass = new $assignmentclass();
1901 return $ass->add_instance($assignment);
1902}
f466c9ed 1903
73097f07 1904
7af1e882 1905/**
1906 * Returns an outline of a user interaction with an assignment
1907 *
1908 * This is done by calling the user_outline() method of the assignment type class
1909 */
73097f07 1910function assignment_user_outline($course, $user, $mod, $assignment) {
1911 global $CFG;
1912
1913 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1914 $assignmentclass = "assignment_$assignment->assignmenttype";
1915 $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
1916 return $ass->user_outline($user);
1917}
1918
7af1e882 1919/**
1920 * Prints the complete info about a user's interaction with an assignment
1921 *
1922 * This is done by calling the user_complete() method of the assignment type class
1923 */
73097f07 1924function assignment_user_complete($course, $user, $mod, $assignment) {
1925 global $CFG;
1926
1927 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1928 $assignmentclass = "assignment_$assignment->assignmenttype";
1929 $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
1930 return $ass->user_complete($user);
1931}
1932
7af1e882 1933/**
1934 * Function to be run periodically according to the moodle cron
1935 *
1936 * Finds all assignment notifications that have yet to be mailed out, and mails them
1937 */
73097f07 1938function assignment_cron () {
73097f07 1939
1940 global $CFG, $USER;
1941
d014b69b 1942 /// first execute all crons in plugins
1943 if ($plugins = get_list_of_plugins('mod/assignment/type')) {
1944 foreach ($plugins as $plugin) {
1945 require_once("$CFG->dirroot/mod/assignment/type/$plugin/assignment.class.php");
1946 $assignmentclass = "assignment_$plugin";
1947 $ass = new $assignmentclass();
1948 $ass->cron();
1949 }
1950 }
1951
73097f07 1952 /// Notices older than 1 day will not be mailed. This is to avoid the problem where
1953 /// cron has not been running for a long time, and then suddenly people are flooded
1954 /// with mail from the past few weeks or months
1955
1956 $timenow = time();
1957 $endtime = $timenow - $CFG->maxeditingtime;
1958 $starttime = $endtime - 24 * 3600; /// One day earlier
1959
1960 if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
1961
98be6ed8 1962 $CFG->enablerecordcache = true; // We want all the caching we can get
1963
1964 $realuser = clone($USER);
1965
73097f07 1966 foreach ($submissions as $key => $submission) {
1967 if (! set_field("assignment_submissions", "mailed", "1", "id", "$submission->id")) {
1968 echo "Could not update the mailed field for id $submission->id. Not mailed.\n";
1969 unset($submissions[$key]);
1970 }
1971 }
1972
1973 $timenow = time();
1974
1975 foreach ($submissions as $submission) {
1976
1977 echo "Processing assignment submission $submission->id\n";
1978
1979 if (! $user = get_record("user", "id", "$submission->userid")) {
1980 echo "Could not find user $post->userid\n";
1981 continue;
1982 }
1983
73097f07 1984 if (! $course = get_record("course", "id", "$submission->course")) {
1985 echo "Could not find course $submission->course\n";
1986 continue;
1987 }
98be6ed8 1988
1989 /// Override the language and timezone of the "current" user, so that
1990 /// mail is customised for the receiver.
1991 $USER = $user;
1992 course_setup($course);
1993
dbbb712e 1994 if (!has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $submission->course), $user->id)) {
6ba65fa0 1995 echo fullname($user)." not an active participant in " . format_string($course->shortname) . "\n";
73097f07 1996 continue;
1997 }
1998
1999 if (! $teacher = get_record("user", "id", "$submission->teacher")) {
2000 echo "Could not find teacher $submission->teacher\n";
2001 continue;
2002 }
2003
2004 if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
2005 echo "Could not find course module for assignment id $submission->assignment\n";
2006 continue;
2007 }
2008
2009 if (! $mod->visible) { /// Hold mail notification for hidden assignments until later
2010 continue;
2011 }
2012
2013 $strassignments = get_string("modulenameplural", "assignment");
2014 $strassignment = get_string("modulename", "assignment");
2015
98be6ed8 2016 $assignmentinfo = new object();
73097f07 2017 $assignmentinfo->teacher = fullname($teacher);
2018 $assignmentinfo->assignment = format_string($submission->name,true);
2019 $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
2020
2021 $postsubject = "$course->shortname: $strassignments: ".format_string($submission->name,true);
2022 $posttext = "$course->shortname -> $strassignments -> ".format_string($submission->name,true)."\n";
2023 $posttext .= "---------------------------------------------------------------------\n";
3f19bff3 2024 $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
73097f07 2025 $posttext .= "---------------------------------------------------------------------\n";
2026
2027 if ($user->mailformat == 1) { // HTML
2028 $posthtml = "<p><font face=\"sans-serif\">".
2029 "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> ->".
2030 "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
2031 "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
2032 $posthtml .= "<hr /><font face=\"sans-serif\">";
2033 $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
2034 $posthtml .= "</font><hr />";
2035 } else {
2036 $posthtml = "";
2037 }
2038
2039 if (! email_to_user($user, $teacher, $postsubject, $posttext, $posthtml)) {
2040 echo "Error: assignment cron: Could not send out mail for id $submission->id to user $user->id ($user->email)\n";
2041 }
2042 }
98be6ed8 2043
2044 $USER = $realuser;
2045 course_setup(SITEID); // reset cron user language, theme and timezone settings
2046
73097f07 2047 }
2048
2049 return true;
2050}
2051
45fa3412 2052/**
2053 * Return grade for given user or all users.
2054 *
2055 * @param int $assignmentid id of assignment
2056 * @param int $userid optional user id, 0 means all users
2057 * @return array array of grades, false if none
2058 */
612607bd 2059function assignment_get_user_grades($assignment, $userid=0) {
45fa3412 2060 global $CFG;
2061
2062 $user = $userid ? "AND u.id = $userid" : "";
2063
ced5ee59 2064 $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat,
2065 s.teacher AS usermodified, s.timemarked AS dategraded, s.timemodified AS datesubmitted
45fa3412 2066 FROM {$CFG->prefix}user u, {$CFG->prefix}assignment_submissions s
612607bd 2067 WHERE u.id = s.userid AND s.assignment = $assignment->id
4a1be95c 2068 $user";
45fa3412 2069
2070 return get_records_sql($sql);
2071}
2072
2073/**
2074 * Update grades by firing grade_updated event
2075 *
612607bd 2076 * @param object $assignment null means all assignments
45fa3412 2077 * @param int $userid specific user only, 0 mean all
2078 */
612607bd 2079function assignment_update_grades($assignment=null, $userid=0, $nullifnone=true) {
45fa3412 2080 global $CFG;
612607bd 2081 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2082 require_once($CFG->libdir.'/gradelib.php');
2083 }
45fa3412 2084
612607bd 2085 if ($assignment != null) {
2086 if ($grades = assignment_get_user_grades($assignment, $userid)) {
2087 foreach($grades as $k=>$v) {
ac9b0805 2088 if ($v->rawgrade == -1) {
2089 $grades[$k]->rawgrade = null;
45fa3412 2090 }
45fa3412 2091 }
ced5ee59 2092 assignment_grade_item_update($assignment, $grades);
45fa3412 2093 }
2094
2095 } else {
4a1be95c 2096 $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
2097 FROM {$CFG->prefix}assignment a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
2098 WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
45fa3412 2099 if ($rs = get_recordset_sql($sql)) {
03cedd62 2100 while ($assignment = rs_fetch_next_record($rs)) {
0b7870f3 2101 if ($assignment->grade != 0) {
03cedd62 2102 assignment_update_grades($assignment);
ced5ee59 2103 } else {
2104 assignment_grade_item_update($assignment);
45fa3412 2105 }
2106 }
2107 rs_close($rs);
2108 }
2109 }
2110}
2111
2112/**
612607bd 2113 * Create grade item for given assignment
45fa3412 2114 *
8b4fb44e 2115 * @param object $assignment object with extra cmidnumber
ced5ee59 2116 * @param mixed optional array/object of grade(s)
612607bd 2117 * @return int 0 if ok, error code otherwise
45fa3412 2118 */
ced5ee59 2119function assignment_grade_item_update($assignment, $grades=NULL) {
612607bd 2120 global $CFG;
2121 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2122 require_once($CFG->libdir.'/gradelib.php');
45fa3412 2123 }
2124
8b4fb44e 2125 if (!isset($assignment->courseid)) {
2126 $assignment->courseid = $assignment->course;
2127 }
2128
612607bd 2129 $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
45fa3412 2130
2131 if ($assignment->grade > 0) {
2132 $params['gradetype'] = GRADE_TYPE_VALUE;
2133 $params['grademax'] = $assignment->grade;
2134 $params['grademin'] = 0;
2135
2136 } else if ($assignment->grade < 0) {
2137 $params['gradetype'] = GRADE_TYPE_SCALE;
2138 $params['scaleid'] = -$assignment->grade;
2139
2140 } else {
612607bd 2141 $params['gradetype'] = GRADE_TYPE_NONE;
45fa3412 2142 }
2143
ced5ee59 2144 return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades, $params);
45fa3412 2145}
2146
2147/**
2148 * Delete grade item for given assignment
2149 *
8b4fb44e 2150 * @param object $assignment object
612607bd 2151 * @return object assignment
45fa3412 2152 */
2153function assignment_grade_item_delete($assignment) {
612607bd 2154 global $CFG;
2155 require_once($CFG->libdir.'/gradelib.php');
2156
8b4fb44e 2157 if (!isset($assignment->courseid)) {
2158 $assignment->courseid = $assignment->course;
2159 }
2160
b67ec72f 2161 return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
45fa3412 2162}
2163
7af1e882 2164/**
2165 * Returns the users with data in one assignment (students and teachers)
2166 *
2167 * @param $assignmentid int
2168 * @return array of user objects
2169 */
73097f07 2170function assignment_get_participants($assignmentid) {
73097f07 2171
2172 global $CFG;
2173
2174 //Get students
2175 $students = get_records_sql("SELECT DISTINCT u.id, u.id
2176 FROM {$CFG->prefix}user u,
2177 {$CFG->prefix}assignment_submissions a
2178 WHERE a.assignment = '$assignmentid' and
2179 u.id = a.userid");
2180 //Get teachers
2181 $teachers = get_records_sql("SELECT DISTINCT u.id, u.id
2182 FROM {$CFG->prefix}user u,
2183 {$CFG->prefix}assignment_submissions a
2184 WHERE a.assignment = '$assignmentid' and
2185 u.id = a.teacher");
2186
2187 //Add teachers to students
2188 if ($teachers) {
2189 foreach ($teachers as $teacher) {
2190 $students[$teacher->id] = $teacher;
2191 }
2192 }
2193 //Return students array (it contains an array of unique users)
2194 return ($students);
2195}
2196
7af1e882 2197/**
2198 * Checks if a scale is being used by an assignment
2199 *
2200 * This is used by the backup code to decide whether to back up a scale
2201 * @param $assignmentid int
2202 * @param $scaleid int
2203 * @return boolean True if the scale is used by the assignment
2204 */
85c9ebb9 2205function assignment_scale_used($assignmentid, $scaleid) {
73097f07 2206
2207 $return = false;
2208
2209 $rec = get_record('assignment','id',$assignmentid,'grade',-$scaleid);
2210
2211 if (!empty($rec) && !empty($scaleid)) {
2212 $return = true;
2213 }
2214
2215 return $return;
2216}
2217
85c9ebb9 2218/**
2219 * Checks if scale is being used by any instance of assignment
2220 *
2221 * This is used to find out if scale used anywhere
2222 * @param $scaleid int
2223 * @return boolean True if the scale is used by any assignment
2224 */
2225function assignment_scale_used_anywhere($scaleid) {
2226 if ($scaleid and record_exists('assignment', 'grade', -$scaleid)) {
2227 return true;
2228 } else {
2229 return false;
2230 }
2231}
2232
7af1e882 2233/**
2234 * Make sure up-to-date events are created for all assignment instances
2235 *
2236 * This standard function will check all instances of this module
2237 * and make sure there are up-to-date events created for each of them.
2238 * If courseid = 0, then every assignment event in the site is checked, else
2239 * only assignment events belonging to the course specified are checked.
2240 * This function is used, in its new format, by restore_refresh_events()
2241 *
2242 * @param $courseid int optional If zero then all assignments for all courses are covered
2243 * @return boolean Always returns true
2244 */
73097f07 2245function assignment_refresh_events($courseid = 0) {
73097f07 2246
2247 if ($courseid == 0) {
2248 if (! $assignments = get_records("assignment")) {
2249 return true;
2250 }
2251 } else {
2252 if (! $assignments = get_records("assignment", "course", $courseid)) {
2253 return true;
2254 }
2255 }
2256 $moduleid = get_field('modules', 'id', 'name', 'assignment');
2257
2258 foreach ($assignments as $assignment) {
2259 $event = NULL;
2260 $event->name = addslashes($assignment->name);
2261 $event->description = addslashes($assignment->description);
2262 $event->timestart = $assignment->timedue;
2263
2264 if ($event->id = get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id)) {
2265 update_event($event);
2266
2267 } else {
2268 $event->courseid = $assignment->course;
2269 $event->groupid = 0;
2270 $event->userid = 0;
2271 $event->modulename = 'assignment';
2272 $event->instance = $assignment->id;
2273 $event->eventtype = 'due';
2274 $event->timeduration = 0;
2275 $event->visible = get_field('course_modules', 'visible', 'module', $moduleid, 'instance', $assignment->id);
2276 add_event($event);
2277 }
2278
2279 }
2280 return true;
2281}
2282
7af1e882 2283/**
2284 * Print recent activity from all assignments in a given course
2285 *
2286 * This is used by the recent activity block
2287 */
73097f07 2288function assignment_print_recent_activity($course, $isteacher, $timestart) {
2289 global $CFG;
2290
2291 $content = false;
70b5660a 2292 $assignments = array();
73097f07 2293
2294 if (!$logs = get_records_select('log', 'time > \''.$timestart.'\' AND '.
2295 'course = \''.$course->id.'\' AND '.
2296 'module = \'assignment\' AND '.
2297 'action = \'upload\' ', 'time ASC')) {
2298 return false;
2299 }
2300
2301 foreach ($logs as $log) {
2302 //Create a temp valid module structure (course,id)
70b5660a 2303 $tempmod = new object();
73097f07 2304 $tempmod->course = $log->course;
2305 $tempmod->id = $log->info;
2306 //Obtain the visible property from the instance
2307 $modvisible = instance_is_visible($log->module,$tempmod);
2308
2309 //Only if the mod is visible
2310 if ($modvisible) {
70b5660a 2311 if ($info = assignment_log_info($log)) {
2312 $assignments[$log->info] = $info;
2313 $assignments[$log->info]->time = $log->time;
2314 $assignments[$log->info]->url = str_replace('&', '&amp;', $log->url);
2315 }
73097f07 2316 }
2317 }
2318
70b5660a 2319 if (!empty($assignments)) {
73097f07 2320 print_headline(get_string('newsubmissions', 'assignment').':');
2321 foreach ($assignments as $assignment) {
583b57b4 2322 print_recent_activity_note($assignment->time, $assignment, $assignment->name,
73097f07 2323 $CFG->wwwroot.'/mod/assignment/'.$assignment->url);
2324 }
2325 $content = true;
2326 }
2327
2328 return $content;
2329}
2330
2331
7af1e882 2332/**
2333 * Returns all assignments since a given time.
2334 *
2335 * If assignment is specified then this restricts the results
2336 */
73097f07 2337function assignment_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $assignment="0", $user="", $groupid="") {
73097f07 2338
2339 global $CFG;
2340
2341 if ($assignment) {
2342 $assignmentselect = " AND cm.id = '$assignment'";
2343 } else {
2344 $assignmentselect = "";
2345 }
2346 if ($user) {
2347 $userselect = " AND u.id = '$user'";
2348 } else {
2349 $userselect = "";
2350 }
2351
2352 $assignments = get_records_sql("SELECT asub.*, u.firstname, u.lastname, u.picture, u.id as userid,
2353 a.grade as maxgrade, name, cm.instance, cm.section, a.assignmenttype
2354 FROM {$CFG->prefix}assignment_submissions asub,
2355 {$CFG->prefix}user u,
2356 {$CFG->prefix}assignment a,
2357 {$CFG->prefix}course_modules cm
2358 WHERE asub.timemodified > '$sincetime'
2359 AND asub.userid = u.id $userselect
2360 AND a.id = asub.assignment $assignmentselect
2361 AND cm.course = '$courseid'
2362 AND cm.instance = a.id
2363 ORDER BY asub.timemodified ASC");
2364
2365 if (empty($assignments))
2366 return;
2367
2368 foreach ($assignments as $assignment) {
2c386f82 2369 if (empty($groupid) || groups_is_member($groupid, $assignment->userid)) {
73097f07 2370
2371 $tmpactivity = new Object;
2372
2373 $tmpactivity->type = "assignment";
2374 $tmpactivity->defaultindex = $index;
2375 $tmpactivity->instance = $assignment->instance;
2376 $tmpactivity->name = $assignment->name;
2377 $tmpactivity->section = $assignment->section;
2378
2379 $tmpactivity->content->grade = $assignment->grade;
2380 $tmpactivity->content->maxgrade = $assignment->maxgrade;
2381 $tmpactivity->content->type = $assignment->assignmenttype;
2382
2383 $tmpactivity->user->userid = $assignment->userid;
2384 $tmpactivity->user->fullname = fullname($assignment);
2385 $tmpactivity->user->picture = $assignment->picture;
2386
2387 $tmpactivity->timestamp = $assignment->timemodified;
2388
2389 $activities[] = $tmpactivity;
2390
2391 $index++;
2392 }
2393 }
2394
2395 return;
2396}
2397
7af1e882 2398/**
2399 * Print recent activity from all assignments in a given course
2400 *
2401 * This is used by course/recent.php
2402 */
73097f07 2403function assignment_print_recent_mod_activity($activity, $course, $detail=false) {
2404 global $CFG;
2405
2406 echo '<table border="0" cellpadding="3" cellspacing="0">';
2407
141a922c 2408 echo "<tr><td class=\"userpicture\" valign=\"top\">";
73097f07 2409 print_user_picture($activity->user->userid, $course, $activity->user->picture);
2410 echo "</td><td width=\"100%\"><font size=2>";
2411
2412 if ($detail) {
2413 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
0d905d9f 2414 "class=\"icon\" alt=\"$activity->type\"> ";
73097f07 2415 echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=" . $activity->instance . "\">"
2416 . format_string($activity->name,true) . "</a> - ";
2417
2418 }
2419
0bde6300 2420 if (has_capability('moodle/course:viewrecent', get_context_instance(CONTEXT_COURSE, $course))) {
73097f07 2421 $grades = "(" . $activity->content->grade . " / " . $activity->content->maxgrade . ") ";
2422
2423 $assignment->id = $activity->instance;
2424 $assignment->course = $course;
2425 $user->id = $activity->user->userid;
2426
2427 echo $grades;
2428 echo "<br />";
2429 }
2430 echo "<a href=\"$CFG->wwwroot/user/view.php?id="
2431 . $activity->user->userid . "&amp;course=$course\">"
2432 . $activity->user->fullname . "</a> ";
2433
2434 echo " - " . userdate($activity->timestamp);
2435
2436 echo "</font></td></tr>";
2437 echo "</table>";
73097f07 2438}
2439
2440/// GENERIC SQL FUNCTIONS
2441
7af1e882 2442/**
2443 * Fetch info from logs
2444 *
2445 * @param $log object with properties ->info (the assignment id) and ->userid
2446 * @return array with assignment name and user firstname and lastname
2447 */
73097f07 2448function assignment_log_info($log) {
2449 global $CFG;
2450 return get_record_sql("SELECT a.name, u.firstname, u.lastname
45fa3412 2451 FROM {$CFG->prefix}assignment a,
73097f07 2452 {$CFG->prefix}user u
45fa3412 2453 WHERE a.id = '$log->info'
73097f07 2454 AND u.id = '$log->userid'");
2455}
2456
7af1e882 2457/**
2458 * Return list of marked submissions that have not been mailed out for currently enrolled students
2459 *
2460 * @return array
2461 */
73097f07 2462function assignment_get_unmailed_submissions($starttime, $endtime) {
7af1e882 2463
73097f07 2464 global $CFG;
45fa3412 2465
73097f07 2466 return get_records_sql("SELECT s.*, a.course, a.name
45fa3412 2467 FROM {$CFG->prefix}assignment_submissions s,
4be6bced 2468 {$CFG->prefix}assignment a
45fa3412 2469 WHERE s.mailed = 0
2470 AND s.timemarked <= $endtime
ea8158c1 2471 AND s.timemarked >= $starttime
4be6bced 2472 AND s.assignment = a.id");
ea8158c1 2473
2474 /* return get_records_sql("SELECT s.*, a.course, a.name
45fa3412 2475 FROM {$CFG->prefix}assignment_submissions s,
73097f07 2476 {$CFG->prefix}assignment a,
2477 {$CFG->prefix}user_students us
45fa3412 2478 WHERE s.mailed = 0
2479 AND s.timemarked <= $endtime
73097f07 2480 AND s.timemarked >= $starttime
2481 AND s.assignment = a.id
2482 AND s.userid = us.userid
2483 AND a.course = us.course");
ea8158c1 2484 */
73097f07 2485}
2486
7af1e882 2487/**
2488 * Counts all real assignment submissions by ENROLLED students (not empty ones)
2489 *
2490 * There are also assignment type methods count_real_submissions() wich in the default
2491 * implementation simply call this function.
2492 * @param $groupid int optional If nonzero then count is restricted to this group
2493 * @return int The number of submissions
2494 */
73097f07 2495function assignment_count_real_submissions($assignment, $groupid=0) {
73097f07 2496 global $CFG;
2497
2498 if ($groupid) { /// How many in a particular group?
7bddd4b7 2499 return count_records_sql("SELECT COUNT(DISTINCT gm.userid, gm.groupid)
73097f07 2500 FROM {$CFG->prefix}assignment_submissions a,
1d684195 2501 {$CFG->prefix}groups_members g
f1af7aaa 2502 WHERE a.assignment = $assignment->id
73097f07 2503 AND a.timemodified > 0
f1af7aaa 2504 AND g.groupid = '$groupid'
1d684195 2505 AND a.userid = g.userid ");
73097f07 2506 } else {
1648afb2 2507 $cm = get_coursemodule_from_instance('assignment', $assignment->id);
2508 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
01194b77 2509
1648afb2 2510 // this is all the users with this capability set, in this context or higher
7bddd4b7 2511 if ($users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
01194b77 2512 foreach ($users as $user) {
2513 $array[] = $user->id;
2514 }
2515
2516 $userlists = '('.implode(',',$array).')';
2517
2518 return count_records_sql("SELECT COUNT(*)
2519 FROM {$CFG->prefix}assignment_submissions
45fa3412 2520 WHERE assignment = '$assignment->id'
01194b77 2521 AND timemodified > 0
2522 AND userid IN $userlists ");
2523 } else {
2524 return 0; // no users enroled in course
73097f07 2525 }
73097f07 2526 }
2527}
2528
7af1e882 2529
2530/**
2531 * Return all assignment submissions by ENROLLED students (even empty)
2532 *
2533 * There are also assignment type methods get_submissions() wich in the default
2534 * implementation simply call this function.
2535 * @param $sort string optional field names for the ORDER BY in the sql query
2536 * @param $dir string optional specifying the sort direction, defaults to DESC
2537 * @return array The submission objects indexed by id
2538 */
73097f07 2539function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
2540/// Return all assignment submissions by ENROLLED students (even empty)
2541 global $CFG;
2542
2543 if ($sort == "lastname" or $sort == "firstname") {
2544 $sort = "u.$sort $dir";
2545 } else if (empty($sort)) {
2546 $sort = "a.timemodified DESC";
2547 } else {
2548 $sort = "a.$sort $dir";
2549 }
2550
ea8158c1 2551 /* not sure this is needed at all since assignmenet already has a course define, so this join?
73097f07 2552 $select = "s.course = '$assignment->course' AND";
2553 if ($assignment->course == SITEID) {
2554 $select = '';
ea8158c1 2555 }*/
45fa3412 2556
2557 return get_records_sql("SELECT a.*
2558 FROM {$CFG->prefix}assignment_submissions a,
ea8158c1 2559 {$CFG->prefix}user u
2560 WHERE u.id = a.userid
45fa3412 2561 AND a.assignment = '$assignment->id'
ea8158c1 2562 ORDER BY $sort");
45fa3412 2563
2564 /* return get_records_sql("SELECT a.*
2565 FROM {$CFG->prefix}assignment_submissions a,
73097f07 2566 {$CFG->prefix}user_students s,
2567 {$CFG->prefix}user u
2568 WHERE a.userid = s.userid
2569 AND u.id = a.userid
45fa3412 2570 AND $select a.assignment = '$assignment->id'
73097f07 2571 ORDER BY $sort");
ea8158c1 2572 */
73097f07 2573}
2574
09ba8e56 2575/**
2576 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
2577 * for the course (see resource).
2578 *
206ab184 2579 * Given a course_module object, this function returns any "extra" information that may be needed
09ba8e56 2580 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
206ab184 2581 *
09ba8e56 2582 * @param $coursemodule object The coursemodule object (record).
2583 * @return object An object on information that the coures will know about (most noticeably, an icon).
206ab184 2584 *
09ba8e56 2585 */
2586function assignment_get_coursemodule_info($coursemodule) {
2587 global $CFG;
2588
2589 if (! $assignment = get_record('assignment', 'id', $coursemodule->instance)) {
2590 return false;
2591 }
2592
2593 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2594 $assignmentclass = "assignment_$assignment->assignmenttype";
2595 $ass = new $assignmentclass($coursemodule->id, $assignment);
2596
2597 return $ass->get_coursemodule_info($coursemodule);
2598}
73097f07 2599
2600
2601
2602/// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS ///////////////////////////////////////
2603
7af1e882 2604/**
2605 * Returns an array of installed assignment types indexed and sorted by name
2606 *
2607 * @return array The index is the name of the assignment type, the value its full name from the language strings
2608 */
b0f2597e 2609function assignment_types() {
2610 $types = array();
2611 $names = get_list_of_plugins('mod/assignment/type');
2612 foreach ($names as $name) {
2613 $types[$name] = get_string('type'.$name, 'assignment');
ffeca120 2614 }
b0f2597e 2615 asort($types);
2616 return $types;
f466c9ed 2617}
2618
7af1e882 2619/**
2620 * Executes upgrade scripts for assignment types when necessary
2621 */
b0f2597e 2622function assignment_upgrade_submodules() {
a86a538f 2623
e9d46b81 2624 global $CFG;
59c005b7 2625
e9d46b81 2626/// Install/upgrade assignment types (it uses, simply, the standard plugin architecture)
2627 upgrade_plugins('assignment_type', 'mod/assignment/type', "$CFG->wwwroot/$CFG->admin/index.php");
59c005b7 2628
59c005b7 2629}
2630
84fa8f5f 2631function assignment_print_overview($courses, &$htmlarray) {
afe35ec4 2632
84fa8f5f 2633 global $USER, $CFG;
2634
2635 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
2636 return array();
2637 }
2638
2639 if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
2640 return;
2641 }
2642
2643 // Do assignment_base::isopen() here without loading the whole thing for speed
2644 foreach ($assignments as $key => $assignment) {
2645 $time = time();
d6da4a1a 2646 if ($assignment->timedue) {
2647 if ($assignment->preventlate) {
2648 $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
2649 } else {
2650 $isopen = ($assignment->timeavailable <= $time);
2651 }
84fa8f5f 2652 }
d6da4a1a 2653 if (empty($isopen) || empty($assignment->timedue)) {
84fa8f5f 2654 unset($assignments[$key]);
2655 }
2656 }
2657
2658 $strduedate = get_string('duedate', 'assignment');
8f643c81 2659 $strduedateno = get_string('duedateno', 'assignment');
84fa8f5f 2660 $strgraded = get_string('graded', 'assignment');
2661 $strnotgradedyet = get_string('notgradedyet', 'assignment');
2662 $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
2663 $strsubmitted = get_string('submitted', 'assignment');
76a60031 2664 $strassignment = get_string('modulename', 'assignment');
c664a036 2665 $strreviewed = get_string('reviewed','assignment');
84fa8f5f 2666
2667 foreach ($assignments as $assignment) {
a2a37336 2668 $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
a7a74d77 2669 '<a '.($assignment->visible ? '':' class="dimmed"').
76a60031 2670 'title="'.$strassignment.'" href="'.$CFG->wwwroot.
a7a74d77 2671 '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
5d45c04f 2672 $assignment->name.'</a></div>';
8f643c81 2673 if ($assignment->timedue) {
2674 $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
2675 } else {
2676 $str .= '<div class="info">'.$strduedateno.'</div>';
2677 }
793a8c3a 2678 $context = get_context_instance(CONTEXT_MODULE, $assignment->coursemodule);
0468976c 2679 if (has_capability('mod/assignment:grade', $context)) {
45fa3412 2680
ea8158c1 2681 // count how many people can submit
2682 $submissions = 0; // init
7bddd4b7 2683 if ($students = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2684 foreach ($students as $student) {
afe35ec4 2685 if (get_records_sql("SELECT id,id FROM {$CFG->prefix}assignment_submissions
2686 WHERE assignment = $assignment->id AND
2687 userid = $student->id AND
2688 teacher = 0 AND
2689 timemarked = 0")) {
45fa3412 2690 $submissions++;
c2adcc90 2691 }
ea8158c1 2692 }
2693 }
45fa3412 2694
84fa8f5f 2695 if ($submissions) {
2696 $str .= get_string('submissionsnotgraded', 'assignment', $submissions);
2697 }
2698 } else {
2699 $sql = "SELECT *
2700 FROM {$CFG->prefix}assignment_submissions
b3d4840d 2701 WHERE userid = '$USER->id'
2702 AND assignment = '{$assignment->id}'";
84fa8f5f 2703 if ($submission = get_record_sql($sql)) {
2704 if ($submission->teacher == 0 && $submission->timemarked == 0) {
2705 $str .= $strsubmitted . ', ' . $strnotgradedyet;
c664a036 2706 } else if ($submission->grade <= 0) {
2707 $str .= $strsubmitted . ', ' . $strreviewed;
84fa8f5f 2708 } else {
2709 $str .= $strsubmitted . ', ' . $strgraded;
2710 }
2711 } else {
76a60031 2712 $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
84fa8f5f 2713 }
2714 }
a7a74d77 2715 $str .= '</div>';
76a60031 2716 if (empty($htmlarray[$assignment->course]['assignment'])) {
2717 $htmlarray[$assignment->course]['assignment'] = $str;
2718 } else {
2719 $htmlarray[$assignment->course]['assignment'] .= $str;
2720 }
2721 }
2722}
2723
2724function assignment_display_lateness($timesubmitted, $timedue) {
2725 if (!$timedue) {
2726 return '';
2727 }
2728 $time = $timedue - $timesubmitted;
2729 if ($time < 0) {
2730 $timetext = get_string('late', 'assignment', format_time($time));
2731 return ' (<span class="late">'.$timetext.'</span>)';
2732 } else {
2733 $timetext = get_string('early', 'assignment', format_time($time));
2734 return ' (<span class="early">'.$timetext.'</span>)';
84fa8f5f 2735 }
2736}
2737
ac15a2d5 2738function assignment_get_view_actions() {
2739 return array('view');
2740}
2741
2742function assignment_get_post_actions() {
2743 return array('upload');
2744}
2745
89bfeee0 2746function assignment_get_types() {
0ac1cfc3 2747 global $CFG;
89bfeee0 2748 $types = array();
2749
2750 $type = new object();
2751 $type->modclass = MOD_CLASS_ACTIVITY;
2752 $type->type = "assignment_group_start";
2753 $type->typestr = '--'.get_string('modulenameplural', 'assignment');
2754 $types[] = $type;
2755
2756 $standardassignments = array('upload','online','uploadsingle','offline');
2757 foreach ($standardassignments as $assignmenttype) {
2758 $type = new object();
2759 $type->modclass = MOD_CLASS_ACTIVITY;
2760 $type->type = "assignment&amp;type=$assignmenttype";
2761 $type->typestr = get_string("type$assignmenttype", 'assignment');
2762 $types[] = $type;
2763 }
2764
2765 /// Drop-in extra assignment types
2766 $assignmenttypes = get_list_of_plugins('mod/assignment/type');
2767 foreach ($assignmenttypes as $assignmenttype) {
2768 if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) { // Not wanted
2769 continue;
2770 }
2771 if (!in_array($assignmenttype, $standardassignments)) {
2772 $type = new object();
2773 $type->modclass = MOD_CLASS_ACTIVITY;
2774 $type->type = "assignment&amp;type=$assignmenttype";
2775 $type->typestr = get_string("type$assignmenttype", 'assignment');
2776 $types[] = $type;
2777 }
2778 }
2779
2780 $type = new object();
2781 $type->modclass = MOD_CLASS_ACTIVITY;
2782 $type->type = "assignment_group_end";
2783 $type->typestr = '--';
2784 $types[] = $type;
2785
2786 return $types;
2787}
2788
76a60031 2789?>