[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/mod/assignment/ -> lib.php (source)

   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">&nbsp;</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.'&amp;userid='.$submission->userid.'&amp;mode=single'.'&amp;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.'&amp;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.'">&nbsp;</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.'">&nbsp;</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.'">&nbsp;</div>';
1239                      $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1240                      $status          = '<div id="st'.$auser->id.'">&nbsp;</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.'">&nbsp;</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                             . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;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 . '&amp;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 (