| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?PHP // $Id$ 2 /** 3 * assignment_base is the base class for assignment types 4 * 5 * This class provides all the functionality for an assignment 6 */ 7 8 DEFINE ('ASSIGNMENT_COUNT_WORDS', 1); 9 DEFINE ('ASSIGNMENT_COUNT_LETTERS', 2); 10 11 /** 12 * Standard base class for all assignment submodules (assignment types). 13 */ 14 class assignment_base { 15 16 var $cm; 17 var $course; 18 var $assignment; 19 var $strassignment; 20 var $strassignments; 21 var $strsubmissions; 22 var $strlastmodified; 23 var $pagetitle; 24 var $usehtmleditor; 25 var $defaultformat; 26 var $context; 27 var $type; 28 29 /** 30 * Constructor for the base assignment class 31 * 32 * Constructor for the base assignment class. 33 * If cmid is set create the cm, course, assignment objects. 34 * If the assignment is hidden and the user is not a teacher then 35 * this prints a page header and notice. 36 * 37 * @param cmid integer, the current course module id - not set for new assignments 38 * @param assignment object, usually null, but if we have it we pass it to save db access 39 * @param cm object, usually null, but if we have it we pass it to save db access 40 * @param course object, usually null, but if we have it we pass it to save db access 41 */ 42 function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) { 43 global $COURSE; 44 45 if ($cmid == 'staticonly') { 46 //use static functions only! 47 return; 48 } 49 50 global $CFG; 51 52 if ($cm) { 53 $this->cm = $cm; 54 } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) { 55 error('Course Module ID was incorrect'); 56 } 57 58 $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id); 59 60 if ($course) { 61 $this->course = $course; 62 } else if ($this->cm->course == $COURSE->id) { 63 $this->course = $COURSE; 64 } else if (! $this->course = get_record('course', 'id', $this->cm->course)) { 65 error('Course is misconfigured'); 66 } 67 68 if ($assignment) { 69 $this->assignment = $assignment; 70 } else if (! $this->assignment = get_record('assignment', 'id', $this->cm->instance)) { 71 error('assignment ID was incorrect'); 72 } 73 74 $this->assignment->cmidnumber = $this->cm->id; // compatibility with modedit assignment obj 75 $this->assignment->courseid = $this->course->id; // compatibility with modedit assignment obj 76 77 $this->strassignment = get_string('modulename', 'assignment'); 78 $this->strassignments = get_string('modulenameplural', 'assignment'); 79 $this->strsubmissions = get_string('submissions', 'assignment'); 80 $this->strlastmodified = get_string('lastmodified'); 81 $this->pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment.': '.format_string($this->assignment->name,true)); 82 83 // visibility handled by require_login() with $cm parameter 84 // get current group only when really needed 85 86 /// Set up things for a HTML editor if it's needed 87 if ($this->usehtmleditor = can_use_html_editor()) { 88 $this->defaultformat = FORMAT_HTML; 89 } else { 90 $this->defaultformat = FORMAT_MOODLE; 91 } 92 } 93 94 /** 95 * Display the assignment, used by view.php 96 * 97 * This in turn calls the methods producing individual parts of the page 98 */ 99 function view() { 100 101 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id); 102 require_capability('mod/assignment:view', $context); 103 104 add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}", 105 $this->assignment->id, $this->cm->id); 106 107 $this->view_header(); 108 109 $this->view_intro(); 110 111 $this->view_dates(); 112 113 $this->view_feedback(); 114 115 $this->view_footer(); 116 } 117 118 /** 119 * Display the header and top of a page 120 * 121 * (this doesn't change much for assignment types) 122 * This is used by the view() method to print the header of view.php but 123 * it can be used on other pages in which case the string to denote the 124 * page in the navigation trail should be passed as an argument 125 * 126 * @param $subpage string Description of subpage to be used in navigation trail 127 */ 128 function view_header($subpage='') { 129 130 global $CFG; 131 132 133 if ($subpage) { 134 $navigation = build_navigation($subpage, $this->cm); 135 } else { 136 $navigation = build_navigation('', $this->cm); 137 } 138 139 print_header($this->pagetitle, $this->course->fullname, $navigation, '', '', 140 true, update_module_button($this->cm->id, $this->course->id, $this->strassignment), 141 navmenu($this->course, $this->cm)); 142 143 groups_print_activity_menu($this->cm, 'view.php?id=' . $this->cm->id); 144 145 echo '<div class="reportlink">'.$this->submittedlink().'</div>'; 146 echo '<div class="clearer"></div>'; 147 } 148 149 150 /** 151 * Display the assignment intro 152 * 153 * This will most likely be extended by assignment type plug-ins 154 * The default implementation prints the assignment description in a box 155 */ 156 function view_intro() { 157 print_simple_box_start('center', '', '', 0, 'generalbox', 'intro'); 158 $formatoptions = new stdClass; 159 $formatoptions->noclean = true; 160 echo format_text($this->assignment->description, $this->assignment->format, $formatoptions); 161 print_simple_box_end(); 162 } 163 164 /** 165 * Display the assignment dates 166 * 167 * Prints the assignment start and end dates in a box. 168 * This will be suitable for most assignment types 169 */ 170 function view_dates() { 171 if (!$this->assignment->timeavailable && !$this->assignment->timedue) { 172 return; 173 } 174 175 print_simple_box_start('center', '', '', 0, 'generalbox', 'dates'); 176 echo '<table>'; 177 if ($this->assignment->timeavailable) { 178 echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>'; 179 echo ' <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>'; 180 } 181 if ($this->assignment->timedue) { 182 echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>'; 183 echo ' <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>'; 184 } 185 echo '</table>'; 186 print_simple_box_end(); 187 } 188 189 190 /** 191 * Display the bottom and footer of a page 192 * 193 * This default method just prints the footer. 194 * This will be suitable for most assignment types 195 */ 196 function view_footer() { 197 print_footer($this->course); 198 } 199 200 /** 201 * Display the feedback to the student 202 * 203 * This default method prints the teacher picture and name, date when marked, 204 * grade and teacher submissioncomment. 205 * 206 * @param $submission object The submission object or NULL in which case it will be loaded 207 */ 208 function view_feedback($submission=NULL) { 209 global $USER, $CFG; 210 require_once($CFG->libdir.'/gradelib.php'); 211 212 if (!has_capability('mod/assignment:submit', $this->context, $USER->id, false)) { 213 // can not submit assignments -> no feedback 214 return; 215 } 216 217 if (!$submission) { /// Get submission for this assignment 218 $submission = $this->get_submission($USER->id); 219 } 220 221 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $USER->id); 222 $item = $grading_info->items[0]; 223 $grade = $item->grades[$USER->id]; 224 225 if ($grade->hidden or $grade->grade === false) { // hidden or error 226 return; 227 } 228 229 if ($grade->grade === null and empty($grade->str_feedback)) { /// Nothing to show yet 230 return; 231 } 232 233 $graded_date = $grade->dategraded; 234 $graded_by = $grade->usermodified; 235 236 /// We need the teacher info 237 if (!$teacher = get_record('user', 'id', $graded_by)) { 238 error('Could not find the teacher'); 239 } 240 241 /// Print the feedback 242 print_heading(get_string('feedbackfromteacher', 'assignment', $this->course->teacher)); // TODO: fix teacher string 243 244 echo '<table cellspacing="0" class="feedback">'; 245 246 echo '<tr>'; 247 echo '<td class="left picture">'; 248 if ($teacher) { 249 print_user_picture($teacher, $this->course->id, $teacher->picture); 250 } 251 echo '</td>'; 252 echo '<td class="topic">'; 253 echo '<div class="from">'; 254 if ($teacher) { 255 echo '<div class="fullname">'.fullname($teacher).'</div>'; 256 } 257 echo '<div class="time">'.userdate($graded_date).'</div>'; 258 echo '</div>'; 259 echo '</td>'; 260 echo '</tr>'; 261 262 echo '<tr>'; 263 echo '<td class="left side"> </td>'; 264 echo '<td class="content">'; 265 echo '<div class="grade">'; 266 echo get_string("grade").': '.$grade->str_long_grade; 267 echo '</div>'; 268 echo '<div class="clearer"></div>'; 269 270 echo '<div class="comment">'; 271 echo $grade->str_feedback; 272 echo '</div>'; 273 echo '</tr>'; 274 275 echo '</table>'; 276 } 277 278 /** 279 * Returns a link with info about the state of the assignment submissions 280 * 281 * This is used by view_header to put this link at the top right of the page. 282 * For teachers it gives the number of submitted assignments with a link 283 * For students it gives the time of their submission. 284 * This will be suitable for most assignment types. 285 * @param bool $allgroup print all groups info if user can access all groups, suitable for index.php 286 * @return string 287 */ 288 function submittedlink($allgroups=false) { 289 global $USER; 290 291 $submitted = ''; 292 293 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id); 294 if (has_capability('mod/assignment:grade', $context)) { 295 if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) { 296 $group = 0; 297 } else { 298 $group = groups_get_activity_group($this->cm); 299 } 300 if ($count = $this->count_real_submissions($group)) { 301 $submitted = '<a href="submissions.php?id='.$this->cm->id.'">'. 302 get_string('viewsubmissions', 'assignment', $count).'</a>'; 303 } else { 304 $submitted = '<a href="submissions.php?id='.$this->cm->id.'">'. 305 get_string('noattempts', 'assignment').'</a>'; 306 } 307 } else { 308 if (!empty($USER->id)) { 309 if ($submission = $this->get_submission($USER->id)) { 310 if ($submission->timemodified) { 311 if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) { 312 $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>'; 313 } else { 314 $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>'; 315 } 316 } 317 } 318 } 319 } 320 321 return $submitted; 322 } 323 324 325 function setup_elements(&$mform) { 326 327 } 328 329 /** 330 * Create a new assignment activity 331 * 332 * Given an object containing all the necessary data, 333 * (defined by the form in mod.html) this function 334 * will create a new instance and return the id number 335 * of the new instance. 336 * The due data is added to the calendar 337 * This is common to all assignment types. 338 * 339 * @param $assignment object The data from the form on mod.html 340 * @return int The id of the assignment 341 */ 342 function add_instance($assignment) { 343 global $COURSE; 344 345 $assignment->timemodified = time(); 346 $assignment->courseid = $assignment->course; 347 348 if ($returnid = insert_record("assignment", $assignment)) { 349 $assignment->id = $returnid; 350 351 if ($assignment->timedue) { 352 $event = new object(); 353 $event->name = $assignment->name; 354 $event->description = $assignment->description; 355 $event->courseid = $assignment->course; 356 $event->groupid = 0; 357 $event->userid = 0; 358 $event->modulename = 'assignment'; 359 $event->instance = $returnid; 360 $event->eventtype = 'due'; 361 $event->timestart = $assignment->timedue; 362 $event->timeduration = 0; 363 364 add_event($event); 365 } 366 367 $assignment = stripslashes_recursive($assignment); 368 assignment_grade_item_update($assignment); 369 370 } 371 372 373 return $returnid; 374 } 375 376 /** 377 * Deletes an assignment activity 378 * 379 * Deletes all database records, files and calendar events for this assignment. 380 * @param $assignment object The assignment to be deleted 381 * @return boolean False indicates error 382 */ 383 function delete_instance($assignment) { 384 global $CFG; 385 386 $assignment->courseid = $assignment->course; 387 388 $result = true; 389 390 if (! delete_records('assignment_submissions', 'assignment', $assignment->id)) { 391 $result = false; 392 } 393 394 if (! delete_records('assignment', 'id', $assignment->id)) { 395 $result = false; 396 } 397 398 if (! delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id)) { 399 $result = false; 400 } 401 402 // delete file area with all attachments - ignore errors 403 require_once($CFG->libdir.'/filelib.php'); 404 fulldelete($CFG->dataroot.'/'.$assignment->course.'/'.$CFG->moddata.'/assignment/'.$assignment->id); 405 406 assignment_grade_item_delete($assignment); 407 408 return $result; 409 } 410 411 /** 412 * Updates a new assignment activity 413 * 414 * Given an object containing all the necessary data, 415 * (defined by the form in mod.html) this function 416 * will update the assignment instance and return the id number 417 * The due date is updated in the calendar 418 * This is common to all assignment types. 419 * 420 * @param $assignment object The data from the form on mod.html 421 * @return int The assignment id 422 */ 423 function update_instance($assignment) { 424 global $COURSE; 425 426 $assignment->timemodified = time(); 427 428 $assignment->id = $assignment->instance; 429 $assignment->courseid = $assignment->course; 430 431 if (!update_record('assignment', $assignment)) { 432 return false; 433 } 434 435 if ($assignment->timedue) { 436 $event = new object(); 437 438 if ($event->id = get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id)) { 439 440 $event->name = $assignment->name; 441 $event->description = $assignment->description; 442 $event->timestart = $assignment->timedue; 443 444 update_event($event); 445 } else { 446 $event = new object(); 447 $event->name = $assignment->name; 448 $event->description = $assignment->description; 449 $event->courseid = $assignment->course; 450 $event->groupid = 0; 451 $event->userid = 0; 452 $event->modulename = 'assignment'; 453 $event->instance = $assignment->id; 454 $event->eventtype = 'due'; 455 $event->timestart = $assignment->timedue; 456 $event->timeduration = 0; 457 458 add_event($event); 459 } 460 } else { 461 delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id); 462 } 463 464 // get existing grade item 465 $assignment = stripslashes_recursive($assignment); 466 467 assignment_grade_item_update($assignment); 468 469 return true; 470 } 471 472 /** 473 * Update grade item for this submission. 474 */ 475 function update_grade($submission) { 476 assignment_update_grades($this->assignment, $submission->userid); 477 } 478 479 /** 480 * Top-level function for handling of submissions called by submissions.php 481 * 482 * This is for handling the teacher interaction with the grading interface 483 * This should be suitable for most assignment types. 484 * 485 * @param $mode string Specifies the kind of teacher interaction taking place 486 */ 487 function submissions($mode) { 488 ///The main switch is changed to facilitate 489 ///1) Batch fast grading 490 ///2) Skip to the next one on the popup 491 ///3) Save and Skip to the next one on the popup 492 493 //make user global so we can use the id 494 global $USER; 495 496 $mailinfo = optional_param('mailinfo', null, PARAM_BOOL); 497 if (is_null($mailinfo)) { 498 $mailinfo = get_user_preferences('assignment_mailinfo', 0); 499 } else { 500 set_user_preference('assignment_mailinfo', $mailinfo); 501 } 502 503 switch ($mode) { 504 case 'grade': // We are in a popup window grading 505 if ($submission = $this->process_feedback()) { 506 //IE needs proper header with encoding 507 print_header(get_string('feedback', 'assignment').':'.format_string($this->assignment->name)); 508 print_heading(get_string('changessaved')); 509 print $this->update_main_listing($submission); 510 } 511 close_window(); 512 break; 513 514 case 'single': // We are in a popup window displaying submission 515 $this->display_submission(); 516 break; 517 518 case 'all': // Main window, display everything 519 $this->display_submissions(); 520 break; 521 522 case 'fastgrade': 523 ///do the fast grading stuff - this process should work for all 3 subclasses 524 525 $grading = false; 526 $commenting = false; 527 $col = false; 528 if (isset($_POST['submissioncomment'])) { 529 $col = 'submissioncomment'; 530 $commenting = true; 531 } 532 if (isset($_POST['menu'])) { 533 $col = 'menu'; 534 $grading = true; 535 } 536 if (!$col) { 537 //both submissioncomment and grade columns collapsed.. 538 $this->display_submissions(); 539 break; 540 } 541 542 foreach ($_POST[$col] as $id => $unusedvalue){ 543 544 $id = (int)$id; //clean parameter name 545 546 $this->process_outcomes($id); 547 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. 556 557 //for fast grade, we need to check if any changes take place 558 $updatedb = false; 559 560 if ($grading) { 561 $grade = $_POST['menu'][$id]; 562 $updatedb = $updatedb || ($submission->grade != $grade); 563 $submission->grade = $grade; 564 } else { 565 if (!$newsubmission) { 566 unset($submission->grade); // Don't need to update this. 567 } 568 } 569 if ($commenting) { 570 $commentvalue = trim($_POST['submissioncomment'][$id]); 571 $updatedb = $updatedb || ($submission->submissioncomment != stripslashes($commentvalue)); 572 $submission->submissioncomment = $commentvalue; 573 } else { 574 unset($submission->submissioncomment); // Don't need to update this. 575 } 576 577 $submission->teacher = $USER->id; 578 if ($updatedb) { 579 $submission->mailed = (int)(!$mailinfo); 580 } 581 582 $submission->timemarked = time(); 583 584 //if it is not an update, we don't change the last modified time etc. 585 //this will also not write into database if no submissioncomment and grade is entered. 586 587 if ($updatedb){ 588 if ($newsubmission) { 589 if (!isset($submission->submissioncomment)) { 590 $submission->submissioncomment = ''; 591 } 592 if (!$sid = insert_record('assignment_submissions', $submission)) { 593 return false; 594 } 595 $submission->id = $sid; 596 } else { 597 if (!update_record('assignment_submissions', $submission)) { 598 return false; 599 } 600 } 601 602 // triger grade event 603 $this->update_grade($submission); 604 605 //add to log only if updating 606 add_to_log($this->course->id, 'assignment', 'update grades', 607 'submissions.php?id='.$this->assignment->id.'&user='.$submission->userid, 608 $submission->userid, $this->cm->id); 609 } 610 611 } 612 613 $message = notify(get_string('changessaved'), 'notifysuccess', 'center', true); 614 615 $this->display_submissions($message); 616 break; 617 618 619 case 'next': 620 /// We are currently in pop up, but we want to skip to next one without saving. 621 /// This turns out to be similar to a single case 622 /// The URL used is for the next submission. 623 624 $this->display_submission(); 625 break; 626 627 case 'saveandnext': 628 ///We are in pop up. save the current one and go to the next one. 629 //first we save the current changes 630 if ($submission = $this->process_feedback()) { 631 //print_heading(get_string('changessaved')); 632 $extra_javascript = $this->update_main_listing($submission); 633 } 634 635 //then we display the next submission 636 $this->display_submission($extra_javascript); 637 break; 638 639 default: 640 echo "something seriously is wrong!!"; 641 break; 642 } 643 } 644 645 /** 646 * Helper method updating the listing on the main script from popup using javascript 647 * 648 * @param $submission object The submission whose data is to be updated on the main page 649 */ 650 function update_main_listing($submission) { 651 global $SESSION, $CFG; 652 653 $output = ''; 654 655 $perpage = get_user_preferences('assignment_perpage', 10); 656 657 $quickgrade = get_user_preferences('assignment_quickgrade', 0); 658 659 /// Run some Javascript to try and update the parent page 660 $output .= '<script type="text/javascript">'."\n<!--\n"; 661 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) { 662 if ($quickgrade){ 663 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="' 664 .trim($submission->submissioncomment).'";'."\n"; 665 } else { 666 $output.= 'opener.document.getElementById("com'.$submission->userid. 667 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n"; 668 } 669 } 670 671 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) { 672 //echo optional_param('menuindex'); 673 if ($quickgrade){ 674 $output.= 'opener.document.getElementById("menumenu'.$submission->userid. 675 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n"; 676 } else { 677 $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'. 678 $this->display_grade($submission->grade)."\";\n"; 679 } 680 } 681 //need to add student's assignments in there too. 682 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) && 683 $submission->timemodified) { 684 $output.= 'opener.document.getElementById("ts'.$submission->userid. 685 '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n"; 686 } 687 688 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) && 689 $submission->timemarked) { 690 $output.= 'opener.document.getElementById("tt'.$submission->userid. 691 '").innerHTML="'.userdate($submission->timemarked)."\";\n"; 692 } 693 694 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) { 695 $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";'; 696 $buttontext = get_string('update'); 697 $button = link_to_popup_window ('/mod/assignment/submissions.php?id='.$this->cm->id.'&userid='.$submission->userid.'&mode=single'.'&offset='.(optional_param('offset', '', PARAM_INT)-1), 698 'grade'.$submission->userid, $buttontext, 450, 700, $buttontext, 'none', true, 'button'.$submission->userid); 699 $output.= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";'; 700 } 701 702 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid); 703 704 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) { 705 $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid. 706 '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n"; 707 } 708 709 if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) { 710 711 if (!empty($grading_info->outcomes)) { 712 foreach($grading_info->outcomes as $n=>$outcome) { 713 if ($outcome->grades[$submission->userid]->locked) { 714 continue; 715 } 716 717 if ($quickgrade){ 718 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid. 719 '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n"; 720 721 } else { 722 $options = make_grades_menu(-$outcome->scaleid); 723 $options[0] = get_string('nooutcome', 'grades'); 724 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n"; 725 } 726 727 } 728 } 729 } 730 731 $output .= "\n-->\n</script>"; 732 return $output; 733 } 734 735 /** 736 * Return a grade in user-friendly form, whether it's a scale or not 737 * 738 * @param $grade 739 * @return string User-friendly representation of grade 740 */ 741 function display_grade($grade) { 742 743 static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!! 744 745 if ($this->assignment->grade >= 0) { // Normal number 746 if ($grade == -1) { 747 return '-'; 748 } else { 749 return $grade.' / '.$this->assignment->grade; 750 } 751 752 } else { // Scale 753 if (empty($scalegrades[$this->assignment->id])) { 754 if ($scale = get_record('scale', 'id', -($this->assignment->grade))) { 755 $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale); 756 } else { 757 return '-'; 758 } 759 } 760 if (isset($scalegrades[$this->assignment->id][$grade])) { 761 return $scalegrades[$this->assignment->id][$grade]; 762 } 763 return '-'; 764 } 765 } 766 767 /** 768 * Display a single submission, ready for grading on a popup window 769 * 770 * This default method prints the teacher info and submissioncomment box at the top and 771 * the student info and submission at the bottom. 772 * This method also fetches the necessary data in order to be able to 773 * provide a "Next submission" button. 774 * Calls preprocess_submission() to give assignment type plug-ins a chance 775 * to process submissions before they are graded 776 * This method gets its arguments from the page parameters userid and offset 777 */ 778 function display_submission($extra_javascript = '') { 779 780 global $CFG; 781 require_once($CFG->libdir.'/gradelib.php'); 782 require_once($CFG->libdir.'/tablelib.php'); 783 784 $userid = required_param('userid', PARAM_INT); 785 $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student. 786 787 if (!$user = get_record('user', 'id', $userid)) { 788 error('No such user!'); 789 } 790 791 if (!$submission = $this->get_submission($user->id)) { 792 $submission = $this->prepare_new_submission($userid); 793 } 794 if ($submission->timemodified > $submission->timemarked) { 795 $subtype = 'assignmentnew'; 796 } else { 797 $subtype = 'assignmentold'; 798 } 799 800 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id)); 801 $disabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden; 802 803 /// construct SQL, using current offset to find the data of the next student 804 $course = $this->course; 805 $assignment = $this->assignment; 806 $cm = $this->cm; 807 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 808 809 /// Get all ppl that can submit assignments 810 811 $currentgroup = groups_get_activity_group($cm); 812 if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $currentgroup, '', false)) { 813 $users = array_keys($users); 814 } 815 816 // if groupmembersonly used, remove users who are not in any group 817 if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) { 818 if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) { 819 $users = array_intersect($users, array_keys($groupingusers)); 820 } 821 } 822 823 $nextid = 0; 824 825 if ($users) { 826 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, u.imagealt, 827 s.id AS submissionid, s.grade, s.submissioncomment, 828 s.timemodified, s.timemarked, 829 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status '; 830 $sql = 'FROM '.$CFG->prefix.'user u '. 831 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid 832 AND s.assignment = '.$this->assignment->id.' '. 833 'WHERE u.id IN ('.implode(',', $users).') '; 834 835 if ($sort = flexible_table::get_sql_sort('mod-assignment-submissions')) { 836 $sort = 'ORDER BY '.$sort.' '; 837 } 838 839 if (($auser = get_records_sql($select.$sql.$sort, $offset+1, 1)) !== false) { 840 $nextuser = array_shift($auser); 841 /// Calculate user status 842 $nextuser->status = ($nextuser->timemarked > 0) && ($nextuser->timemarked >= $nextuser->timemodified); 843 $nextid = $nextuser->id; 844 } 845 } 846 847 print_header(get_string('feedback', 'assignment').':'.fullname($user, true).':'.format_string($this->assignment->name)); 848 849 /// Print any extra javascript needed for saveandnext 850 echo $extra_javascript; 851 852 ///SOme javascript to help with setting up >.> 853 854 echo '<script type="text/javascript">'."\n"; 855 echo 'function setNext(){'."\n"; 856 echo 'document.getElementById(\'submitform\').mode.value=\'next\';'."\n"; 857 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n"; 858 echo '}'."\n"; 859 860 echo 'function saveNext(){'."\n"; 861 echo 'document.getElementById(\'submitform\').mode.value=\'saveandnext\';'."\n"; 862 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n"; 863 echo 'document.getElementById(\'submitform\').saveuserid.value="'.$userid.'";'."\n"; 864 echo 'document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex;'."\n"; 865 echo '}'."\n"; 866 867 echo '</script>'."\n"; 868 echo '<table cellspacing="0" class="feedback '.$subtype.'" >'; 869 870 ///Start of teacher info row 871 872 echo '<tr>'; 873 echo '<td class="picture teacher">'; 874 if ($submission->teacher) { 875 $teacher = get_record('user', 'id', $submission->teacher); 876 } else { 877 global $USER; 878 $teacher = $USER; 879 } 880 print_user_picture($teacher, $this->course->id, $teacher->picture); 881 echo '</td>'; 882 echo '<td class="content">'; 883 echo '<form id="submitform" action="submissions.php" method="post">'; 884 echo '<div>'; // xhtml compatibility - invisiblefieldset was breaking layout here 885 echo '<input type="hidden" name="offset" value="'.($offset+1).'" />'; 886 echo '<input type="hidden" name="userid" value="'.$userid.'" />'; 887 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />'; 888 echo '<input type="hidden" name="mode" value="grade" />'; 889 echo '<input type="hidden" name="menuindex" value="0" />';//selected menu index 890 891 //new hidden field, initialized to -1. 892 echo '<input type="hidden" name="saveuserid" value="-1" />'; 893 894 if ($submission->timemarked) { 895 echo '<div class="from">'; 896 echo '<div class="fullname">'.fullname($teacher, true).'</div>'; 897 echo '<div class="time">'.userdate($submission->timemarked).'</div>'; 898 echo '</div>'; 899 } 900 echo '<div class="grade"><label for="menugrade">'.get_string('grade').'</label> '; 901 choose_from_menu(make_grades_menu($this->assignment->grade), 'grade', $submission->grade, get_string('nograde'), '', -1, false, $disabled); 902 echo '</div>'; 903 904 echo '<div class="clearer"></div>'; 905 echo '<div class="finalgrade">'.get_string('finalgrade', 'grades').': '.$grading_info->items[0]->grades[$userid]->str_grade.'</div>'; 906 echo '<div class="clearer"></div>'; 907 908 if (!empty($CFG->enableoutcomes)) { 909 foreach($grading_info->outcomes as $n=>$outcome) { 910 echo '<div class="outcome"><label for="menuoutcome_'.$n.'">'.$outcome->name.'</label> '; 911 $options = make_grades_menu(-$outcome->scaleid); 912 if ($outcome->grades[$submission->userid]->locked) { 913 $options[0] = get_string('nooutcome', 'grades'); 914 echo $options[$outcome->grades[$submission->userid]->grade]; 915 } else { 916 choose_from_menu($options, 'outcome_'.$n.'['.$userid.']', $outcome->grades[$submission->userid]->grade, get_string('nooutcome', 'grades'), '', 0, false, false, 0, 'menuoutcome_'.$n); 917 } 918 echo '</div>'; 919 echo '<div class="clearer"></div>'; 920 } 921 } 922 923 924 $this->preprocess_submission($submission); 925 926 if ($disabled) { 927 echo '<div class="disabledfeedback">'.$grading_info->items[0]->grades[$userid]->str_feedback.'</div>'; 928 929 } else { 930 print_textarea($this->usehtmleditor, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment, $this->course->id); 931 if ($this->usehtmleditor) { 932 echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />'; 933 } else { 934 echo '<div class="format">'; 935 choose_from_menu(format_text_menu(), "format", $submission->format, ""); 936 helpbutton("textformat", get_string("helpformatting")); 937 echo '</div>'; 938 } 939 } 940 941 $lastmailinfo = get_user_preferences('assignment_mailinfo', 1) ? 'checked="checked"' : ''; 942 943 ///Print Buttons in Single View 944 echo '<input type="hidden" name="mailinfo" value="0" />'; 945 echo '<input type="checkbox" id="mailinfo" name="mailinfo" value="1" '.$lastmailinfo.' /><label for="mailinfo">'.get_string('enableemailnotification','assignment').'</label>'; 946 echo '<div class="buttons">'; 947 echo '<input type="submit" name="submit" value="'.get_string('savechanges').'" onclick = "document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex" />'; 948 echo '<input type="submit" name="cancel" value="'.get_string('cancel').'" />'; 949 //if there are more to be graded. 950 if ($nextid) { 951 echo '<input type="submit" name="saveandnext" value="'.get_string('saveandnext').'" onclick="saveNext()" />'; 952 echo '<input type="submit" name="next" value="'.get_string('next').'" onclick="setNext();" />'; 953 } 954 echo '</div>'; 955 echo '</div></form>'; 956 957 $customfeedback = $this->custom_feedbackform($submission, true); 958 if (!empty($customfeedback)) { 959 echo $customfeedback; 960 } 961 962 echo '</td></tr>'; 963 964 ///End of teacher info row, Start of student info row 965 echo '<tr>'; 966 echo '<td class="picture user">'; 967 print_user_picture($user, $this->course->id, $user->picture); 968 echo '</td>'; 969 echo '<td class="topic">'; 970 echo '<div class="from">'; 971 echo '<div class="fullname">'.fullname($user, true).'</div>'; 972 if ($submission->timemodified) { 973 echo '<div class="time">'.userdate($submission->timemodified). 974 $this->display_lateness($submission->timemodified).'</div>'; 975 } 976 echo '</div>'; 977 $this->print_user_files($user->id); 978 echo '</td>'; 979 echo '</tr>'; 980 981 ///End of student info row 982 983 echo '</table>'; 984 985 if (!$disabled and $this->usehtmleditor) { 986 use_html_editor(); 987 } 988 989 print_footer('none'); 990 } 991 992 /** 993 * Preprocess submission before grading 994 * 995 * Called by display_submission() 996 * The default type does nothing here. 997 * @param $submission object The submission object 998 */ 999 function preprocess_submission(&$submission) { 1000 } 1001 1002 /** 1003 * Display all the submissions ready for grading 1004 */ 1005 function display_submissions($message='') { 1006 global $CFG, $db, $USER; 1007 require_once($CFG->libdir.'/gradelib.php'); 1008 1009 /* first we check to see if the form has just been submitted 1010 * to request user_preference updates 1011 */ 1012 1013 if (isset($_POST['updatepref'])){ 1014 $perpage = optional_param('perpage', 10, PARAM_INT); 1015 $perpage = ($perpage <= 0) ? 10 : $perpage ; 1016 set_user_preference('assignment_perpage', $perpage); 1017 set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL)); 1018 } 1019 1020 /* next we get perpage and quickgrade (allow quick grade) params 1021 * from database 1022 */ 1023 $perpage = get_user_preferences('assignment_perpage', 10); 1024 1025 $quickgrade = get_user_preferences('assignment_quickgrade', 0); 1026 1027 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id); 1028 1029 if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) { 1030 $uses_outcomes = true; 1031 } else { 1032 $uses_outcomes = false; 1033 } 1034 1035 $page = optional_param('page', 0, PARAM_INT); 1036 $strsaveallfeedback = get_string('saveallfeedback', 'assignment'); 1037 1038 /// Some shortcuts to make the code read better 1039 1040 $course = $this->course; 1041 $assignment = $this->assignment; 1042 $cm = $this->cm; 1043 1044 $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet 1045 add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id, $this->assignment->id, $this->cm->id); 1046 $navigation = build_navigation($this->strsubmissions, $this->cm); 1047 print_header_simple(format_string($this->assignment->name,true), "", $navigation, 1048 '', '', true, update_module_button($cm->id, $course->id, $this->strassignment), navmenu($course, $cm)); 1049 1050 $course_context = get_context_instance(CONTEXT_COURSE, $course->id); 1051 if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) { 1052 echo '<div class="allcoursegrades"><a href="' . $CFG->wwwroot . '/grade/report/grader/index.php?id=' . $course->id . '">' 1053 . get_string('seeallcoursegrades', 'grades') . '</a></div>'; 1054 } 1055 1056 if (!empty($message)) { 1057 echo $message; // display messages here if any 1058 } 1059 1060 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 1061 1062 /// Check to see if groups are being used in this assignment 1063 1064 /// find out current groups mode 1065 $groupmode = groups_get_activity_groupmode($cm); 1066 $currentgroup = groups_get_activity_group($cm, true); 1067 groups_print_activity_menu($cm, 'submissions.php?id=' . $this->cm->id); 1068 1069 /// Get all ppl that are allowed to submit assignments 1070 if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $currentgroup, '', false)) { 1071 $users = array_keys($users); 1072 } 1073 1074 // if groupmembersonly used, remove users who are not in any group 1075 if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) { 1076 if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) { 1077 $users = array_intersect($users, array_keys($groupingusers)); 1078 } 1079 } 1080 1081 $tablecolumns = array('picture', 'fullname', 'grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade'); 1082 if ($uses_outcomes) { 1083 $tablecolumns[] = 'outcome'; // no sorting based on outcomes column 1084 } 1085 1086 $tableheaders = array('', 1087 get_string('fullname'), 1088 get_string('grade'), 1089 get_string('comment', 'assignment'), 1090 get_string('lastmodified').' ('.$course->student.')', 1091 get_string('lastmodified').' ('.$course->teacher.')', 1092 get_string('status'), 1093 get_string('finalgrade', 'grades')); 1094 if ($uses_outcomes) { 1095 $tableheaders[] = get_string('outcome', 'grades'); 1096 } 1097 1098 require_once($CFG->libdir.'/tablelib.php'); 1099 $table = new flexible_table('mod-assignment-submissions'); 1100 1101 $table->define_columns($tablecolumns); 1102 $table->define_headers($tableheaders); 1103 $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&currentgroup='.$currentgroup); 1104 1105 $table->sortable(true, 'lastname');//sorted by lastname by default 1106 $table->collapsible(true); 1107 $table->initialbars(true); 1108 1109 $table->column_suppress('picture'); 1110 $table->column_suppress('fullname'); 1111 1112 $table->column_class('picture', 'picture'); 1113 $table->column_class('fullname', 'fullname'); 1114 $table->column_class('grade', 'grade'); 1115 $table->column_class('submissioncomment', 'comment'); 1116 $table->column_class('timemodified', 'timemodified'); 1117 $table->column_class('timemarked', 'timemarked'); 1118 $table->column_class('status', 'status'); 1119 $table->column_class('finalgrade', 'finalgrade'); 1120 if ($uses_outcomes) { 1121 $table->column_class('outcome', 'outcome'); 1122 } 1123 1124 $table->set_attribute('cellspacing', '0'); 1125 $table->set_attribute('id', 'attempts'); 1126 $table->set_attribute('class', 'submissions'); 1127 $table->set_attribute('width', '100%'); 1128 //$table->set_attribute('align', 'center'); 1129 1130 $table->no_sorting('finalgrade'); 1131 $table->no_sorting('outcome'); 1132 1133 // Start working -- this is necessary as soon as the niceties are over 1134 $table->setup(); 1135 1136 if (empty($users)) { 1137 print_heading(get_string('nosubmitusers','assignment')); 1138 return true; 1139 } 1140 1141 /// Construct the SQL 1142 1143 if ($where = $table->get_sql_where()) { 1144 $where .= ' AND '; 1145 } 1146 1147 if ($sort = $table->get_sql_sort()) { 1148 $sort = ' ORDER BY '.$sort; 1149 } 1150 1151 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, u.imagealt, 1152 s.id AS submissionid, s.grade, s.submissioncomment, 1153 s.timemodified, s.timemarked, 1154 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status '; 1155 $sql = 'FROM '.$CFG->prefix.'user u '. 1156 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid 1157 AND s.assignment = '.$this->assignment->id.' '. 1158 'WHERE '.$where.'u.id IN ('.implode(',',$users).') '; 1159 1160 $table->pagesize($perpage, count($users)); 1161 1162 ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next 1163 $offset = $page * $perpage; 1164 1165 $strupdate = get_string('update'); 1166 $strgrade = get_string('grade'); 1167 $grademenu = make_grades_menu($this->assignment->grade); 1168 1169 if (($ausers = get_records_sql($select.$sql.$sort, $table->get_page_start(), $table->get_page_size())) !== false) { 1170 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers)); 1171 foreach ($ausers as $auser) { 1172 $final_grade = $grading_info->items[0]->grades[$auser->id]; 1173 $grademax = $grading_info->items[0]->grademax; 1174 $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2); 1175 $locked_overridden = 'locked'; 1176 if ($final_grade->overridden) { 1177 $locked_overridden = 'overridden'; 1178 } 1179 1180 /// Calculate user status 1181 $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified); 1182 $picture = print_user_picture($auser, $course->id, $auser->picture, false, true); 1183 1184 if (empty($auser->submissionid)) { 1185 $auser->grade = -1; //no submission yet 1186 } 1187 1188 if (!empty($auser->submissionid)) { 1189 ///Prints student answer and student modified date 1190 ///attach file or print link to student answer, depending on the type of the assignment. 1191 ///Refer to print_student_answer in inherited classes. 1192 if ($auser->timemodified > 0) { 1193 $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id) 1194 . userdate($auser->timemodified).'</div>'; 1195 } else { 1196 $studentmodified = '<div id="ts'.$auser->id.'"> </div>'; 1197 } 1198 ///Print grade, dropdown or text 1199 if ($auser->timemarked > 0) { 1200 $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>'; 1201 1202 if ($final_grade->locked or $final_grade->overridden) { 1203 $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>'; 1204 } else if ($quickgrade) { 1205 $menu = choose_from_menu(make_grades_menu($this->assignment->grade), 1206 'menu['.$auser->id.']', $auser->grade, 1207 get_string('nograde'),'',-1,true,false,$tabindex++); 1208 $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>'; 1209 } else { 1210 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>'; 1211 } 1212 1213 } else { 1214 $teachermodified = '<div id="tt'.$auser->id.'"> </div>'; 1215 if ($final_grade->locked or $final_grade->overridden) { 1216 $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>'; 1217 } else if ($quickgrade) { 1218 $menu = choose_from_menu(make_grades_menu($this->assignment->grade), 1219 'menu['.$auser->id.']', $auser->grade, 1220 get_string('nograde'),'',-1,true,false,$tabindex++); 1221 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>'; 1222 } else { 1223 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>'; 1224 } 1225 } 1226 ///Print Comment 1227 if ($final_grade->locked or $final_grade->overridden) { 1228 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>'; 1229 1230 } else if ($quickgrade) { 1231 $comment = '<div id="com'.$auser->id.'">' 1232 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment' 1233 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>'; 1234 } else { 1235 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>'; 1236 } 1237 } else { 1238 $studentmodified = '<div id="ts'.$auser->id.'"> </div>'; 1239 $teachermodified = '<div id="tt'.$auser->id.'"> </div>'; 1240 $status = '<div id="st'.$auser->id.'"> </div>'; 1241 1242 if ($final_grade->locked or $final_grade->overridden) { 1243 $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>'; 1244 } else if ($quickgrade) { // allow editing 1245 $menu = choose_from_menu(make_grades_menu($this->assignment->grade), 1246 'menu['.$auser->id.']', $auser->grade, 1247 get_string('nograde'),'',-1,true,false,$tabindex++); 1248 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>'; 1249 } else { 1250 $grade = '<div id="g'.$auser->id.'">-</div>'; 1251 } 1252 1253 if ($final_grade->locked or $final_grade->overridden) { 1254 $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>'; 1255 } else if ($quickgrade) { 1256 $comment = '<div id="com'.$auser->id.'">' 1257 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment' 1258 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>'; 1259 } else { 1260 $comment = '<div id="com'.$auser->id.'"> </div>'; 1261 } 1262 } 1263 1264 if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1 1265 $auser->status = 0; 1266 } else { 1267 $auser->status = 1; 1268 } 1269 1270 $buttontext = ($auser->status == 1) ? $strupdate : $strgrade; 1271 1272 ///No more buttons, we use popups ;-). 1273 $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id 1274 . '&userid='.$auser->id.'&mode=single'.'&offset='.$offset++; 1275 $button = link_to_popup_window ($popup_url, 'grade'.$auser->id, $buttontext, 600, 780, 1276 $buttontext, 'none', true, 'button'.$auser->id); 1277 1278 $status = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>'; 1279 1280 $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>'; 1281 1282 $outcomes = ''; 1283 1284 if ($uses_outcomes) { 1285 1286 foreach($grading_info->outcomes as $n=>$outcome) { 1287 $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>'; 1288 $options = make_grades_menu(-$outcome->scaleid); 1289 1290 if ($outcome->grades[$auser->id]->locked or !$quickgrade) { 1291 $options[0] = get_string('nooutcome', 'grades'); 1292 $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>'; 1293 } else { 1294 $outcomes .= ' '; 1295 $outcomes .= choose_from_menu($options, 'outcome_'.$n.'['.$auser->id.']', 1296 $outcome->grades[$auser->id]->grade, get_string('nooutcome', 'grades'), '', 0, true, false, 0, 'outcome_'.$n.'_'.$auser->id); 1297 } 1298 $outcomes .= '</div>'; 1299 } 1300 } 1301 1302 $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $auser->id . '&course=' . $course->id . '">' . fullname($auser) . '</a>'; 1303 $row = array($picture, $userlink, $grade, $comment, $studentmodified, $teachermodified, $status, $finalgrade); 1304 if ($uses_outcomes) { 1305 $row[] = $outcomes; 1306 } 1307 1308 $table->add_data($row); 1309 } 1310 } 1311 1312 /// Print quickgrade form around the table 1313 if ($quickgrade){ 1314 echo '<form action="submissions.php" id="fastg" method="post">'; 1315 echo '<div>'; 1316 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />'; 1317 echo '<input type="hidden" name="mode" value="fastgrade" />'; 1318 echo '<input type="hidden" name="page" value="'.$page.'" />'; 1319 echo '</div>'; 1320 } 1321 1322 $table->print_html(); /// Print the whole table 1323 1324 if ($quickgrade){ 1325 $lastmailinfo = get_user_preferences('assignment_mailinfo', 1) ? 'checked="checked"' : ''; 1326 echo '<div class="fgcontrols">'; 1327 echo '<div class="emailnotification">'; 1328 echo '<label for="mailinfo">'.get_string('enableemailnotification','assignment').'</label>'; 1329 echo '<input type="hidden" name="mailinfo" value="0" />'; 1330 echo '<input type="checkbox" id="mailinfo" name="mailinfo" value="1" '.$lastmailinfo.' />'; 1331 helpbutton('emailnotification', get_string('enableemailnotification', 'assignment'), 'assignment').'</p></div>'; 1332 echo '</div>'; 1333 echo '<div class="fastgbutton"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>'; 1334 echo '</div>'; 1335 echo '</form>'; 1336 } 1337 /// End of fast grading form 1338 1339 /// Mini form for setting user preference 1340 echo '<div class="qgprefs">'; 1341 echo '<form id="options" action="submissions.php?id='.$this->cm->id.'" method="post"><div>'; 1342 echo '<input type="hidden" name="updatepref" value="1" />'; 1343 echo '<table id="optiontable">'; 1344 echo '<tr><td>'; 1345 echo '<label for="perpage">'.get_string('pagesize','assignment').'</label>'; 1346 echo '</td>'; 1347 echo '<td>'; 1348 echo '<input type="text" id="perpage" name="perpage" size="1" value="'.$perpage.'" />'; 1349 helpbutton('pagesize', get_string('pagesize','assignment'), 'assignment'); 1350 echo '</td></tr>'; 1351 echo '<tr><td>'; 1352 echo '<label for="quickgrade">'.get_string('quickgrade','assignment').'</label>'; 1353 echo '</td>'; 1354 echo '<td>'; 1355 $checked = $quickgrade ? 'checked="checked"' : ''; 1356 echo '<input type="checkbox" id="quickgrade" name="quickgrade" value="1" '.$checked.' />'; 1357 helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment').'</p></div>'; 1358 echo '</td></tr>'; 1359 echo '<tr><td colspan="2">'; 1360 echo '<input type="submit" value="'.get_string('savepreferences').'" />'; 1361 echo '</td></tr></table>'; 1362 echo '</div></form></div>'; 1363 ///End of mini form 1364 print_footer($this->course); 1365 } 1366 1367 /** 1368 * Process teacher feedback submission 1369 * 1370 * This is called by submissions() when a grading even has taken place. 1371 * It gets its data from the submitted form. 1372 * @return object The updated submission object 1373 */ 1374 function process_feedback() { 1375 global $CFG, $USER; 1376 require_once($CFG->libdir.'/gradelib.php'); 1377 1378 if (!$feedback = data_submitted()) { // No incoming data? 1379 return false; 1380 } 1381 1382 ///For save and next, we need to know the userid to save, and the userid to go 1383 ///We use a new hidden field in the form, and set it to -1. If it's set, we use this 1384 ///as the userid to store 1385 if ((int)$feedback->saveuserid !== -1){ 1386 $feedback->userid = $feedback->saveuserid; 1387 } 1388 1389 if (!empty($feedback->cancel)) { // User hit cancel button 1390 return false; 1391 } 1392 1393 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid); 1394 1395 // store outcomes if needed 1396 $this->process_outcomes($feedback->userid); 1397 1398 $submission = $this->get_submission($feedback->userid, true); // Get or make one 1399 1400 if (!$grading_info->items[0]->grades[$feedback->userid]->locked and 1401 !$grading_info->items[0]->grades[$feedback->userid]->overridden) { 1402 1403 $submission->grade = $feedback->grade; 1404 $submission->submissioncomment = $feedback->submissioncomment; 1405 $submission->format = $feedback->format; 1406 $submission->teacher = $USER->id; 1407 $mailinfo = get_user_preferences('assignment_mailinfo', 0); 1408 if (!$mailinfo) { 1409 $submission->mailed = 1; // treat as already mailed 1410 } else { 1411 $submission->mailed = 0; // Make sure mail goes out (again, even) 1412 } 1413 $submission->timemarked = time(); 1414 1415 unset($submission->data1); // Don't need to update this. 1416 unset($submission->data2); // Don't need to update this. 1417 1418 if (empty($submission->timemodified)) { // eg for offline assignments 1419 // $submission->timemodified = time(); 1420 } 1421 1422 if (! update_record('assignment_submissions', $submission)) { 1423 return false; 1424 } 1425 1426 // triger grade event 1427 $this->update_grade($submission); 1428 1429 add_to_log($this->course->id, 'assignment', 'update grades', 1430 'submissions.php?id='.$this->assignment->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id); 1431 } 1432 1433 return $submission; 1434 1435 } 1436 1437 function process_outcomes($userid) { 1438 global $CFG, $USER; 1439 1440 if (empty($CFG->enableoutcomes)) { 1441 return; 1442 } 1443 1444 require_once($CFG->libdir.'/gradelib.php'); 1445 1446 if (!$formdata = data_submitted()) { 1447 return; 1448 } 1449 1450 $data = array(); 1451 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid); 1452 1453 if (!empty($grading_info->outcomes)) { 1454 foreach($grading_info->outcomes as $n=>$old) { 1455 $name = 'outcome_'.$n; 1456 if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) { 1457 $data[$n] = $formdata->{$name}[$userid]; 1458 } 1459 } 1460 } 1461 if (count($data) > 0) { 1462 grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data); 1463 } 1464 1465 } 1466 1467 /** 1468 * Load the submission object for a particular user 1469 * 1470 * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used 1471 * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database 1472 * @param bool $teachermodified student submission set if false 1473 * @return object The submission 1474 */ 1475 function get_submission($userid=0, $createnew=false, $teachermodified=false) { 1476 global $USER; 1477 1478 if (empty($userid)) { 1479 $userid = $USER->id; 1480 } 1481 1482 $submission = get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid); 1483 1484 if ($submission || !$createnew) { 1485 return $submission; 1486 } 1487 $newsubmission = $this->prepare_new_submission($userid, $teachermodified); 1488 if (!insert_record("assignment_submissions", $newsubmission)) { 1489 error("Could not insert a new empty submission"); 1490 } 1491 1492 return get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid); 1493 } 1494 1495 /** 1496 * Instantiates a new submission object for a given user 1497 * 1498 * Sets the assignment, userid and times, everything else is set to default values. 1499 * @param $userid int The userid for which we want a submission object 1500 * @param bool $teachermodified student submission set if false 1501 * @return object The submission 1502 */ 1503 function prepare_new_submission($userid, $teachermodified=false) { 1504 $submission = new Object; 1505 $submission->assignment = $this->assignment->id; 1506 $submission->userid = $userid; 1507 //$submission->timecreated = time(); 1508 $submission->timecreated = ''; 1509 // teachers should not be modifying modified date, except offline assignments 1510 if ($teachermodified) { 1511 $submission->timemodified = 0; 1512 } else { 1513 $submission->timemodified = $submission->timecreated; 1514 } 1515 $submission->numfiles = 0; 1516 $submission->data1 = ''; 1517 $submission->data2 = ''; 1518 $submission->grade = -1; 1519 $submission->submissioncomment = ''; 1520 $submission->format = 0; 1521 $submission->teacher = 0; 1522 $submission->timemarked = 0; 1523 $submission->mailed = 0; 1524 return $submission; 1525 } 1526 1527 /** 1528 * Return all assignment submissions by ENROLLED students (even empty) 1529 * 1530 * @param $sort string optional field names for the ORDER BY in the sql query 1531 * @param $dir string optional specifying the sort direction, defaults to DESC 1532 * @return array The submission objects indexed by id 1533 */ 1534 function get_submissions($sort='', $dir='DESC') { 1535 return assignment_get_all_submissions($this->assignment, $sort, $dir); 1536 } 1537 1538 /** 1539 * Counts all real assignment submissions by ENROLLED students (not empty ones) 1540 * 1541 * @param $groupid int optional If nonzero then count is restricted to this group 1542 * @return int The number of submissions 1543 */ 1544 function count_real_submissions($groupid=0) { 1545 return assignment_count_real_submissions($this->cm, $groupid); 1546 } 1547 1548 /** 1549 * Alerts teachers by email of new or changed assignments that need grading 1550 * 1551 * First checks whether the option to email teachers is set for this assignment. 1552 * Sends an email to ALL teachers in the course (or in the group if using separate groups). 1553 * Uses the methods email_teachers_text() and email_teachers_html() to construct the content. 1554 * @param $submission object The submission that has changed 1555 */ 1556 function email_teachers($submission) { 1557 global $CFG; 1558 1559 if (empty($this->assignment->emailteachers)) { // No need to do anything 1560 return; 1561 } 1562 1563 $user = get_record('user', 'id', $submission->userid); 1564 1565 if ($teachers = $this->get_graders($user)) { 1566 1567 $strassignments = get_string('modulenameplural', 'assignment'); 1568 $strassignment = get_string('modulename', 'assignment'); 1569 $strsubmitted = get_string('submitted', 'assignment'); 1570 1571 foreach ($teachers as $teacher) { 1572 $info = new object(); 1573 $info->username = fullname($user, true); 1574 $info->assignment = format_string($this->assignment->name,true); 1575 $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id; 1576 1577 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name; 1578 $posttext = $this->email_teachers_text($info); 1579 $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : ''; 1580 1581 @email_to_user($teacher, $user, $postsubject, $posttext, $posthtml); // If it fails, oh well, too bad. 1582 } 1583 } 1584 } 1585 1586 /** 1587 * Returns a list of teachers that should be grading given submission 1588 */ 1589 function get_graders($user) { 1590 //potential graders 1591 $potgraders = get_users_by_capability($this->context, 'mod/assignment:grade', '', '', '', '', '', '', false, false); 1592 1593 $graders = array(); 1594 if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) { // Separate groups are being used 1595 if ($groups = groups_get_all_groups($this->course->id, $user->id)) { // Try to find all groups 1596 foreach ($groups as $group) { 1597 foreach ($potgraders as $t) { 1598 if ($t->id == $user->id) { 1599 continue; // do not send self 1600 } 1601 if (groups_is_member($group->id, $t->id)) { 1602 $graders[$t->id] = $t; 1603 } 1604 } 1605 } 1606 } else { 1607 // user not in group, try to find graders without group 1608 foreach ($potgraders as $t) { 1609 if ($t->id == $user->id) { 1610 continue; // do not send self 1611 } 1612 if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack 1613 $graders[$t->id] = $t; 1614 } 1615 } 1616 } 1617 } else { 1618 foreach ($potgraders as $t) { 1619 if ($t->id == $user->id) { 1620 continue; // do not send self 1621 } 1622 $graders[$t->id] = $t; 1623 } 1624 } 1625 return $graders; 1626 } 1627 1628 /** 1629 * Creates the text content for emails to teachers 1630 * 1631 * @param $info object The info used by the 'emailteachermail' language string 1632 * @return string 1633 */ 1634 function email_teachers_text($info) { 1635 $posttext = format_string($this->course->shortname).' -> '.$this->strassignments.' -> '. 1636 format_string($this->assignment->name)."\n"; 1637 $posttext .= '---------------------------------------------------------------------'."\n"; 1638 $posttext .= get_string("emailteachermail", "assignment", $info)."\n"; 1639 $posttext .= "\n---------------------------------------------------------------------\n"; 1640 return $posttext; 1641 } 1642 1643 /** 1644 * Creates the html content for emails to teachers 1645 * 1646 * @param $info object The info used by the 'emailteachermailhtml' language string 1647 * @return string 1648 */ 1649 function email_teachers_html($info) { 1650 global $CFG; 1651 $posthtml = '<p><font face="sans-serif">'. 1652 '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname).'</a> ->'. 1653 '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'. 1654 '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name).'</a></font></p>'; 1655 $posthtml .= '<hr /><font face="sans-serif">'; 1656 $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>'; 1657 $posthtml .= '</font><hr />'; 1658 return $posthtml; 1659 } 1660 1661 /** 1662 * Produces a list of links to the files uploaded by a user 1663 * 1664 * @param $userid int optional id of the user. If 0 then $USER->id is used. 1665 * @param $return boolean optional defaults to false. If true the list is returned rather than printed 1666 * @return string optional 1667 */ 1668 function print_user_files($userid=0, $return=false) { 1669 global $CFG, $USER; 1670 1671 if (!$userid) { 1672 if (!isloggedin()) { 1673 return ''; 1674 } 1675 $userid = $USER->id; 1676 } 1677 1678 $filearea = $this->file_area_name($userid); 1679 1680 $output = ''; 1681 1682 if ($basedir = $this->file_area($userid)) { 1683 if ($files = get_directory_list($basedir)) { 1684 require_once($CFG->libdir.'/filelib.php'); 1685 foreach ($files as $key => $file) { 1686 1687 $icon = mimeinfo('icon', $file); 1688 $ffurl = get_file_url("$filearea/$file", array('forcedownload'=>1)); 1689 1690 $output .= '<img src="'.$CFG->pixpath.'/f/'.$icon.'" class="icon" alt="'.$icon.'" />'. 1691 '<a href="'.$ffurl.'" >'.$file.'</a><br />'; 1692 } 1693 } 1694 } 1695 1696 $output = '<div class="files">'.$output.'</div>'; 1697 1698 if ($return) { 1699 return $output; 1700 } 1701 echo $output; 1702 } 1703 1704 /** 1705 * Count the files uploaded by a given user 1706 * 1707 * @param $userid int The user id 1708 * @return int 1709 */ 1710 function count_user_files($userid) { 1711 global $CFG; 1712 1713 $filearea = $this->file_area_name($userid); 1714 1715 if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) { 1716 if ($files = get_directory_list($basedir)) { 1717 return count($files); 1718 } 1719 } 1720 return 0; 1721 } 1722 1723 /** 1724 * Creates a directory file name, suitable for make_upload_directory() 1725 * 1726 * @param $userid int The user id 1727 * @return string path to file area 1728 */ 1729 function file_area_name($userid) { 1730 global $CFG; 1731 1732 return $this->course->id.'/'.$CFG->moddata.'/assignment/'.$this->assignment->id.'/'.$userid; 1733 } 1734 1735 /** 1736 * Makes an upload directory 1737 * 1738 * @param $userid int The user id 1739 * @return string path to file area. 1740 */ 1741 function file_area($userid) { 1742 return make_upload_directory( $this->file_area_name($userid) ); 1743 } 1744 1745 /** 1746 * Returns true if the student is allowed to submit 1747 * 1748 * Checks that the assignment has started and, if the option to prevent late 1749 * submissions is set, also checks that the assignment has not yet closed. 1750 * @return boolean 1751 */ 1752 function isopen() { 1753 $time = time(); 1754 if ($this->assignment->preventlate && $this->assignment->timedue) { 1755 return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue); 1756 } else { 1757 return ($this->assignment->timeavailable <= $time); 1758 } 1759 } 1760 1761 1762 /** 1763 * Return true if is set description is hidden till available date 1764 * 1765 * This is needed by calendar so that hidden descriptions do not 1766 * come up in upcoming events. 1767 * 1768 * Check that description is hidden till available date 1769 * By default return false 1770 * Assignments types should implement this method if needed 1771 * @return boolen 1772 */ 1773 function description_is_hidden() { 1774 return false; 1775 } 1776 1777 /** 1778 * Return an outline of the user's interaction with the assignment 1779 * 1780 * The default method prints the grade and timemodified 1781 * @param $user object 1782 * @return object with properties ->info and ->time 1783 */ 1784 function user_outline($user) { 1785 if ($submission = $this->get_submission($user->id)) { 1786 1787 $result = new object(); 1788 $result->info = get_string('grade').': '.$this->display_grade($submission->grade); 1789 $result->time = $submission->timemodified; 1790 return $result; 1791 } 1792 return NULL; 1793 } 1794 1795 /** 1796 * Print complete information about the user's interaction with the assignment 1797 * 1798 * @param $user object 1799 */ 1800 function user_complete($user) { 1801 if (