Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file contains the definition for the grading table which subclassses easy_table
  19   *
  20   * @package   mod_assign
  21   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once($CFG->libdir.'/tablelib.php');
  28  require_once($CFG->libdir.'/gradelib.php');
  29  require_once($CFG->dirroot.'/mod/assign/locallib.php');
  30  
  31  /**
  32   * Extends table_sql to provide a table of assignment submissions
  33   *
  34   * @package   mod_assign
  35   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class assign_grading_table extends table_sql implements renderable {
  39      /** @var assign $assignment */
  40      private $assignment = null;
  41      /** @var int $perpage */
  42      private $perpage = 10;
  43      /** @var int $rownum (global index of current row in table) */
  44      private $rownum = -1;
  45      /** @var renderer_base for getting output */
  46      private $output = null;
  47      /** @var stdClass gradinginfo */
  48      private $gradinginfo = null;
  49      /** @var int $tablemaxrows */
  50      private $tablemaxrows = 10000;
  51      /** @var boolean $quickgrading */
  52      private $quickgrading = false;
  53      /** @var boolean $hasgrantextension - Only do the capability check once for the entire table */
  54      private $hasgrantextension = false;
  55      /** @var boolean $hasgrade - Only do the capability check once for the entire table */
  56      private $hasgrade = false;
  57      /** @var array $groupsubmissions - A static cache of group submissions */
  58      private $groupsubmissions = array();
  59      /** @var array $submissiongroups - A static cache of submission groups */
  60      private $submissiongroups = array();
  61      /** @var string $plugingradingbatchoperations - List of plugin supported batch operations */
  62      public $plugingradingbatchoperations = array();
  63      /** @var array $plugincache - A cache of plugin lookups to match a column name to a plugin efficiently */
  64      private $plugincache = array();
  65      /** @var array $scale - A list of the keys and descriptions for the custom scale */
  66      private $scale = null;
  67  
  68      /**
  69       * overridden constructor keeps a reference to the assignment class that is displaying this table
  70       *
  71       * @param assign $assignment The assignment class
  72       * @param int $perpage how many per page
  73       * @param string $filter The current filter
  74       * @param int $rowoffset For showing a subsequent page of results
  75       * @param bool $quickgrading Is this table wrapped in a quickgrading form?
  76       * @param string $downloadfilename
  77       */
  78      public function __construct(assign $assignment,
  79                                  $perpage,
  80                                  $filter,
  81                                  $rowoffset,
  82                                  $quickgrading,
  83                                  $downloadfilename = null) {
  84          global $CFG, $PAGE, $DB, $USER;
  85          parent::__construct('mod_assign_grading');
  86          $this->is_persistent(true);
  87          $this->assignment = $assignment;
  88  
  89          // Check permissions up front.
  90          $this->hasgrantextension = has_capability('mod/assign:grantextension',
  91                                                    $this->assignment->get_context());
  92          $this->hasgrade = $this->assignment->can_grade();
  93  
  94          // Check if we have the elevated view capablities to see the blind details.
  95          $this->hasviewblind = has_capability('mod/assign:viewblinddetails',
  96                  $this->assignment->get_context());
  97  
  98          foreach ($assignment->get_feedback_plugins() as $plugin) {
  99              if ($plugin->is_visible() && $plugin->is_enabled()) {
 100                  foreach ($plugin->get_grading_batch_operations() as $action => $description) {
 101                      if (empty($this->plugingradingbatchoperations)) {
 102                          $this->plugingradingbatchoperations[$plugin->get_type()] = array();
 103                      }
 104                      $this->plugingradingbatchoperations[$plugin->get_type()][$action] = $description;
 105                  }
 106              }
 107          }
 108          $this->perpage = $perpage;
 109          $this->quickgrading = $quickgrading && $this->hasgrade;
 110          $this->output = $PAGE->get_renderer('mod_assign');
 111  
 112          $urlparams = array('action' => 'grading', 'id' => $assignment->get_course_module()->id);
 113          $url = new moodle_url($CFG->wwwroot . '/mod/assign/view.php', $urlparams);
 114          $this->define_baseurl($url);
 115  
 116          // Do some business - then set the sql.
 117          $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
 118  
 119          if ($rowoffset) {
 120              $this->rownum = $rowoffset - 1;
 121          }
 122  
 123          $users = array_keys( $assignment->list_participants($currentgroup, true));
 124          if (count($users) == 0) {
 125              // Insert a record that will never match to the sql is still valid.
 126              $users[] = -1;
 127          }
 128  
 129          $params = array();
 130          $params['assignmentid1'] = (int)$this->assignment->get_instance()->id;
 131          $params['assignmentid2'] = (int)$this->assignment->get_instance()->id;
 132          $params['assignmentid3'] = (int)$this->assignment->get_instance()->id;
 133          $params['newstatus'] = ASSIGN_SUBMISSION_STATUS_NEW;
 134  
 135          $extrauserfields = get_extra_user_fields($this->assignment->get_context());
 136  
 137          $fields = user_picture::fields('u', $extrauserfields) . ', ';
 138          $fields .= 'u.id as userid, ';
 139          $fields .= 's.status as status, ';
 140          $fields .= 's.id as submissionid, ';
 141          $fields .= 's.timecreated as firstsubmission, ';
 142          $fields .= "CASE WHEN status <> :newstatus THEN s.timemodified ELSE NULL END as timesubmitted, ";
 143          $fields .= 's.attemptnumber as attemptnumber, ';
 144          $fields .= 'g.id as gradeid, ';
 145          $fields .= 'g.grade as grade, ';
 146          $fields .= 'g.timemodified as timemarked, ';
 147          $fields .= 'g.timecreated as firstmarked, ';
 148          $fields .= 'uf.mailed as mailed, ';
 149          $fields .= 'uf.locked as locked, ';
 150          $fields .= 'uf.extensionduedate as extensionduedate, ';
 151          $fields .= 'uf.workflowstate as workflowstate, ';
 152          $fields .= 'uf.allocatedmarker as allocatedmarker';
 153  
 154          $from = '{user} u
 155                           LEFT JOIN {assign_submission} s
 156                                  ON u.id = s.userid
 157                                 AND s.assignment = :assignmentid1
 158                                 AND s.latest = 1 ';
 159  
 160          // For group assignments, there can be a grade with no submission.
 161          $from .= ' LEFT JOIN {assign_grades} g
 162                              ON g.assignment = :assignmentid2
 163                             AND u.id = g.userid
 164                             AND (g.attemptnumber = s.attemptnumber OR s.attemptnumber IS NULL) ';
 165  
 166          $from .= 'LEFT JOIN {assign_user_flags} uf
 167                           ON u.id = uf.userid
 168                          AND uf.assignment = :assignmentid3 ';
 169  
 170          if ($this->assignment->get_course()->relativedatesmode) {
 171              $params['courseid1'] = $this->assignment->get_course()->id;
 172              $from .= ' LEFT JOIN (
 173              SELECT ue1.userid as enroluserid,
 174                CASE WHEN MIN(ue1.timestart - c2.startdate) < 0 THEN 0 ELSE MIN(ue1.timestart - c2.startdate) END as enrolstartoffset
 175                FROM {enrol} e1
 176                JOIN {user_enrolments} ue1
 177                  ON (ue1.enrolid = e1.id AND ue1.status = 0)
 178                JOIN {course} c2
 179                  ON c2.id = e1.courseid
 180               WHERE e1.courseid = :courseid1 AND e1.status = 0
 181               GROUP BY ue1.userid
 182              ) enroloffset
 183              ON (enroloffset.enroluserid = u.id) ';
 184          }
 185  
 186          $hasoverrides = $this->assignment->has_overrides();
 187          $inrelativedatesmode = $this->assignment->get_course()->relativedatesmode;
 188  
 189          if ($hasoverrides) {
 190              $params['assignmentid5'] = (int)$this->assignment->get_instance()->id;
 191              $params['assignmentid6'] = (int)$this->assignment->get_instance()->id;
 192              $params['assignmentid7'] = (int)$this->assignment->get_instance()->id;
 193              $params['assignmentid8'] = (int)$this->assignment->get_instance()->id;
 194              $params['assignmentid9'] = (int)$this->assignment->get_instance()->id;
 195  
 196              list($userwhere1, $userparams1) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'priorityuser');
 197              list($userwhere2, $userparams2) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'effectiveuser');
 198  
 199              $userwhere1 = "WHERE u.id {$userwhere1}";
 200              $userwhere2 = "WHERE u.id {$userwhere2}";
 201              $params = array_merge($params, $userparams1);
 202              $params = array_merge($params, $userparams2);
 203  
 204              $fields .= ', priority.priority, ';
 205              $fields .= 'effective.allowsubmissionsfromdate, ';
 206  
 207              if ($inrelativedatesmode) {
 208                  // If the priority is less than the 9999999 constant value it means it's an override
 209                  // and we should use that value directly. Otherwise we need to apply the uesr's course
 210                  // start date offset.
 211                  $fields .= 'CASE WHEN priority.priority < 9999999 THEN effective.duedate ELSE' .
 212                             ' effective.duedate + enroloffset.enrolstartoffset END as duedate, ';
 213              } else {
 214                  $fields .= 'effective.duedate, ';
 215              }
 216  
 217              $fields .= 'effective.cutoffdate ';
 218  
 219              $from .= ' LEFT JOIN (
 220                 SELECT merged.userid, min(merged.priority) priority FROM (
 221                    ( SELECT u.id as userid, 9999999 AS priority
 222                        FROM {user} u '.$userwhere1.'
 223                    )
 224                    UNION
 225                    ( SELECT uo.userid, 0 AS priority
 226                        FROM {assign_overrides} uo
 227                       WHERE uo.assignid = :assignmentid5
 228                    )
 229                    UNION
 230                    ( SELECT gm.userid, go.sortorder AS priority
 231                        FROM {assign_overrides} go
 232                        JOIN {groups} g ON g.id = go.groupid
 233                        JOIN {groups_members} gm ON gm.groupid = g.id
 234                       WHERE go.assignid = :assignmentid6
 235                    )
 236                  ) merged
 237                  GROUP BY merged.userid
 238                ) priority ON priority.userid = u.id
 239  
 240              JOIN (
 241                (SELECT 9999999 AS priority,
 242                        u.id AS userid,
 243                        a.allowsubmissionsfromdate,
 244                        a.duedate,
 245                        a.cutoffdate
 246                   FROM {user} u
 247                   JOIN {assign} a ON a.id = :assignmentid7
 248                   '.$userwhere2.'
 249                )
 250                UNION
 251                (SELECT 0 AS priority,
 252                        uo.userid,
 253                        uo.allowsubmissionsfromdate,
 254                        uo.duedate,
 255                        uo.cutoffdate
 256                   FROM {assign_overrides} uo
 257                  WHERE uo.assignid = :assignmentid8
 258                )
 259                UNION
 260                (SELECT go.sortorder AS priority,
 261                        gm.userid,
 262                        go.allowsubmissionsfromdate,
 263                        go.duedate,
 264                        go.cutoffdate
 265                   FROM {assign_overrides} go
 266                   JOIN {groups} g ON g.id = go.groupid
 267                   JOIN {groups_members} gm ON gm.groupid = g.id
 268                  WHERE go.assignid = :assignmentid9
 269                )
 270  
 271              ) effective ON effective.priority = priority.priority AND effective.userid = priority.userid ';
 272          } else if ($inrelativedatesmode) {
 273              // In relative dates mode and when we don't have overrides, include the
 274              // duedate, cutoffdate and allowsubmissionsfrom date anyway as this information is useful and can vary.
 275              $params['assignmentid5'] = (int)$this->assignment->get_instance()->id;
 276              $fields .= ', a.duedate + enroloffset.enrolstartoffset as duedate, ';
 277              $fields .= 'a.allowsubmissionsfromdate, ';
 278              $fields .= 'a.cutoffdate ';
 279              $from .= 'JOIN {assign} a ON a.id = :assignmentid5 ';
 280          }
 281  
 282          if (!empty($this->assignment->get_instance()->blindmarking)) {
 283              $from .= 'LEFT JOIN {assign_user_mapping} um
 284                               ON u.id = um.userid
 285                              AND um.assignment = :assignmentidblind ';
 286              $params['assignmentidblind'] = (int)$this->assignment->get_instance()->id;
 287              $fields .= ', um.id as recordid ';
 288          }
 289  
 290          $userparams3 = array();
 291          $userindex = 0;
 292  
 293          list($userwhere3, $userparams3) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'user');
 294          $where = 'u.id ' . $userwhere3;
 295          $params = array_merge($params, $userparams3);
 296  
 297          // The filters do not make sense when there are no submissions, so do not apply them.
 298          if ($this->assignment->is_any_submission_plugin_enabled()) {
 299              if ($filter == ASSIGN_FILTER_SUBMITTED) {
 300                  $where .= ' AND (s.timemodified IS NOT NULL AND
 301                                   s.status = :submitted) ';
 302                  $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
 303  
 304              } else if ($filter == ASSIGN_FILTER_NOT_SUBMITTED) {
 305                  $where .= ' AND (s.timemodified IS NULL OR s.status <> :submitted) ';
 306                  $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
 307              } else if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) {
 308                  $where .= ' AND (s.timemodified IS NOT NULL AND
 309                                   s.status = :submitted AND
 310                                   (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL';
 311  
 312                  // Assignment grade is set to the negative grade scale id when scales are used.
 313                  if ($this->assignment->get_instance()->grade < 0) {
 314                      // Scale grades are set to -1 when not graded.
 315                      $where .= ' OR g.grade = -1';
 316                  }
 317  
 318                  $where .= '))';
 319                  $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
 320  
 321              } else if ($filter == ASSIGN_FILTER_GRANTED_EXTENSION) {
 322                  $where .= ' AND uf.extensionduedate > 0 ';
 323  
 324              } else if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) {
 325                  $userfilter = (int) array_pop(explode('=', $filter));
 326                  $where .= ' AND (u.id = :userid)';
 327                  $params['userid'] = $userfilter;
 328              }
 329          }
 330  
 331          if ($this->assignment->get_instance()->markingworkflow &&
 332              $this->assignment->get_instance()->markingallocation) {
 333              if (has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
 334                  // Check to see if marker filter is set.
 335                  $markerfilter = (int)get_user_preferences('assign_markerfilter', '');
 336                  if (!empty($markerfilter)) {
 337                      if ($markerfilter == ASSIGN_MARKER_FILTER_NO_MARKER) {
 338                          $where .= ' AND (uf.allocatedmarker IS NULL OR uf.allocatedmarker = 0)';
 339                      } else {
 340                          $where .= ' AND uf.allocatedmarker = :markerid';
 341                          $params['markerid'] = $markerfilter;
 342                      }
 343                  }
 344              }
 345          }
 346  
 347          if ($this->assignment->get_instance()->markingworkflow) {
 348              $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user();
 349              if (!empty($workflowstates)) {
 350                  $workflowfilter = get_user_preferences('assign_workflowfilter', '');
 351                  if ($workflowfilter == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED) {
 352                      $where .= ' AND (uf.workflowstate = :workflowstate OR uf.workflowstate IS NULL OR '.
 353                          $DB->sql_isempty('assign_user_flags', 'workflowstate', true, true).')';
 354                      $params['workflowstate'] = $workflowfilter;
 355                  } else if (array_key_exists($workflowfilter, $workflowstates)) {
 356                      $where .= ' AND uf.workflowstate = :workflowstate';
 357                      $params['workflowstate'] = $workflowfilter;
 358                  }
 359              }
 360          }
 361  
 362          $this->set_sql($fields, $from, $where, $params);
 363  
 364          if ($downloadfilename) {
 365              $this->is_downloading('csv', $downloadfilename);
 366          }
 367  
 368          $columns = array();
 369          $headers = array();
 370  
 371          // Select.
 372          if (!$this->is_downloading() && $this->hasgrade) {
 373              $columns[] = 'select';
 374              $headers[] = get_string('select') .
 375                      '<div class="selectall"><label class="accesshide" for="selectall">' . get_string('selectall') . '</label>
 376                      <input type="checkbox" id="selectall" name="selectall" title="' . get_string('selectall') . '"/></div>';
 377          }
 378  
 379          // User picture.
 380          if ($this->hasviewblind || !$this->assignment->is_blind_marking()) {
 381              if (!$this->is_downloading()) {
 382                  $columns[] = 'picture';
 383                  $headers[] = get_string('pictureofuser');
 384              } else {
 385                  $columns[] = 'recordid';
 386                  $headers[] = get_string('recordid', 'assign');
 387              }
 388  
 389              // Fullname.
 390              $columns[] = 'fullname';
 391              $headers[] = get_string('fullname');
 392  
 393              // Participant # details if can view real identities.
 394              if ($this->assignment->is_blind_marking()) {
 395                  if (!$this->is_downloading()) {
 396                      $columns[] = 'recordid';
 397                      $headers[] = get_string('recordid', 'assign');
 398                  }
 399              }
 400  
 401              foreach ($extrauserfields as $extrafield) {
 402                  $columns[] = $extrafield;
 403                  $headers[] = get_user_field_name($extrafield);
 404              }
 405          } else {
 406              // Record ID.
 407              $columns[] = 'recordid';
 408              $headers[] = get_string('recordid', 'assign');
 409          }
 410  
 411          // Submission status.
 412          $columns[] = 'status';
 413          $headers[] = get_string('status', 'assign');
 414  
 415          if ($hasoverrides || $inrelativedatesmode) {
 416              // Allowsubmissionsfromdate.
 417              $columns[] = 'allowsubmissionsfromdate';
 418              $headers[] = get_string('allowsubmissionsfromdate', 'assign');
 419  
 420              // Duedate.
 421              $columns[] = 'duedate';
 422              $headers[] = get_string('duedate', 'assign');
 423  
 424              // Cutoffdate.
 425              $columns[] = 'cutoffdate';
 426              $headers[] = get_string('cutoffdate', 'assign');
 427          }
 428  
 429          // Team submission columns.
 430          if ($assignment->get_instance()->teamsubmission) {
 431              $columns[] = 'team';
 432              $headers[] = get_string('submissionteam', 'assign');
 433          }
 434          // Allocated marker.
 435          if ($this->assignment->get_instance()->markingworkflow &&
 436              $this->assignment->get_instance()->markingallocation &&
 437              has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
 438              // Add a column for the allocated marker.
 439              $columns[] = 'allocatedmarker';
 440              $headers[] = get_string('marker', 'assign');
 441          }
 442          // Grade.
 443          $columns[] = 'grade';
 444          $headers[] = get_string('grade');
 445          if ($this->is_downloading()) {
 446              $gradetype = $this->assignment->get_instance()->grade;
 447              if ($gradetype > 0) {
 448                  $columns[] = 'grademax';
 449                  $headers[] = get_string('maxgrade', 'assign');
 450              } else if ($gradetype < 0) {
 451                  // This is a custom scale.
 452                  $columns[] = 'scale';
 453                  $headers[] = get_string('scale', 'assign');
 454              }
 455  
 456              if ($this->assignment->get_instance()->markingworkflow) {
 457                  // Add a column for the marking workflow state.
 458                  $columns[] = 'workflowstate';
 459                  $headers[] = get_string('markingworkflowstate', 'assign');
 460              }
 461              // Add a column to show if this grade can be changed.
 462              $columns[] = 'gradecanbechanged';
 463              $headers[] = get_string('gradecanbechanged', 'assign');
 464          }
 465          if (!$this->is_downloading() && $this->hasgrade) {
 466              // We have to call this column userid so we can use userid as a default sortable column.
 467              $columns[] = 'userid';
 468              $headers[] = get_string('edit');
 469          }
 470  
 471          // Submission plugins.
 472          if ($assignment->is_any_submission_plugin_enabled()) {
 473              $columns[] = 'timesubmitted';
 474              $headers[] = get_string('lastmodifiedsubmission', 'assign');
 475  
 476              foreach ($this->assignment->get_submission_plugins() as $plugin) {
 477                  if ($this->is_downloading()) {
 478                      if ($plugin->is_visible() && $plugin->is_enabled()) {
 479                          foreach ($plugin->get_editor_fields() as $field => $description) {
 480                              $index = 'plugin' . count($this->plugincache);
 481                              $this->plugincache[$index] = array($plugin, $field);
 482                              $columns[] = $index;
 483                              $headers[] = $plugin->get_name();
 484                          }
 485                      }
 486                  } else {
 487                      if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
 488                          $index = 'plugin' . count($this->plugincache);
 489                          $this->plugincache[$index] = array($plugin);
 490                          $columns[] = $index;
 491                          $headers[] = $plugin->get_name();
 492                      }
 493                  }
 494              }
 495          }
 496  
 497          // Time marked.
 498          $columns[] = 'timemarked';
 499          $headers[] = get_string('lastmodifiedgrade', 'assign');
 500  
 501          // Feedback plugins.
 502          foreach ($this->assignment->get_feedback_plugins() as $plugin) {
 503              if ($this->is_downloading()) {
 504                  if ($plugin->is_visible() && $plugin->is_enabled()) {
 505                      foreach ($plugin->get_editor_fields() as $field => $description) {
 506                          $index = 'plugin' . count($this->plugincache);
 507                          $this->plugincache[$index] = array($plugin, $field);
 508                          $columns[] = $index;
 509                          $headers[] = $description;
 510                      }
 511                  }
 512              } else if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
 513                  $index = 'plugin' . count($this->plugincache);
 514                  $this->plugincache[$index] = array($plugin);
 515                  $columns[] = $index;
 516                  $headers[] = $plugin->get_name();
 517              }
 518          }
 519  
 520          // Exclude 'Final grade' column in downloaded grading worksheets.
 521          if (!$this->is_downloading()) {
 522              // Final grade.
 523              $columns[] = 'finalgrade';
 524              $headers[] = get_string('finalgrade', 'grades');
 525          }
 526  
 527          // Load the grading info for all users.
 528          $this->gradinginfo = grade_get_grades($this->assignment->get_course()->id,
 529                                                'mod',
 530                                                'assign',
 531                                                $this->assignment->get_instance()->id,
 532                                                $users);
 533  
 534          if (!empty($CFG->enableoutcomes) && !empty($this->gradinginfo->outcomes)) {
 535              $columns[] = 'outcomes';
 536              $headers[] = get_string('outcomes', 'grades');
 537          }
 538  
 539          // Set the columns.
 540          $this->define_columns($columns);
 541          $this->define_headers($headers);
 542          foreach ($extrauserfields as $extrafield) {
 543               $this->column_class($extrafield, $extrafield);
 544          }
 545          $this->no_sorting('recordid');
 546          $this->no_sorting('finalgrade');
 547          $this->no_sorting('userid');
 548          $this->no_sorting('select');
 549          $this->no_sorting('outcomes');
 550  
 551          if ($assignment->get_instance()->teamsubmission) {
 552              $this->no_sorting('team');
 553          }
 554  
 555          $plugincolumnindex = 0;
 556          foreach ($this->assignment->get_submission_plugins() as $plugin) {
 557              if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
 558                  $submissionpluginindex = 'plugin' . $plugincolumnindex++;
 559                  $this->no_sorting($submissionpluginindex);
 560              }
 561          }
 562          foreach ($this->assignment->get_feedback_plugins() as $plugin) {
 563              if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
 564                  $feedbackpluginindex = 'plugin' . $plugincolumnindex++;
 565                  $this->no_sorting($feedbackpluginindex);
 566              }
 567          }
 568  
 569          // When there is no data we still want the column headers printed in the csv file.
 570          if ($this->is_downloading()) {
 571              $this->start_output();
 572          }
 573      }
 574  
 575      /**
 576       * Before adding each row to the table make sure rownum is incremented.
 577       *
 578       * @param array $row row of data from db used to make one row of the table.
 579       * @return array one row for the table
 580       */
 581      public function format_row($row) {
 582          if ($this->rownum < 0) {
 583              $this->rownum = $this->currpage * $this->pagesize;
 584          } else {
 585              $this->rownum += 1;
 586          }
 587  
 588          return parent::format_row($row);
 589      }
 590  
 591      /**
 592       * Add a column with an ID that uniquely identifies this user in this assignment.
 593       *
 594       * @param stdClass $row
 595       * @return string
 596       */
 597      public function col_recordid(stdClass $row) {
 598          if (empty($row->recordid)) {
 599              $row->recordid = $this->assignment->get_uniqueid_for_user($row->userid);
 600          }
 601          return get_string('hiddenuser', 'assign') . $row->recordid;
 602      }
 603  
 604  
 605      /**
 606       * Add the userid to the row class so it can be updated via ajax.
 607       *
 608       * @param stdClass $row The row of data
 609       * @return string The row class
 610       */
 611      public function get_row_class($row) {
 612          return 'user' . $row->userid;
 613      }
 614  
 615      /**
 616       * Return the number of rows to display on a single page.
 617       *
 618       * @return int The number of rows per page
 619       */
 620      public function get_rows_per_page() {
 621          return $this->perpage;
 622      }
 623  
 624      /**
 625       * list current marking workflow state
 626       *
 627       * @param stdClass $row
 628       * @return string
 629       */
 630      public function col_workflowstatus(stdClass $row) {
 631          $o = '';
 632  
 633          $gradingdisabled = $this->assignment->grading_disabled($row->id);
 634          // The function in the assignment keeps a static cache of this list of states.
 635          $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user();
 636          $workflowstate = $row->workflowstate;
 637          if (empty($workflowstate)) {
 638              $workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
 639          }
 640          if ($this->quickgrading && !$gradingdisabled) {
 641              $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
 642              $name = 'quickgrade_' . $row->id . '_workflowstate';
 643              $o .= html_writer::select($workflowstates, $name, $workflowstate, array('' => $notmarked));
 644              // Check if this user is a marker that can't manage allocations and doesn't have the marker column added.
 645              if ($this->assignment->get_instance()->markingworkflow &&
 646                  $this->assignment->get_instance()->markingallocation &&
 647                  !has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
 648  
 649                  $name = 'quickgrade_' . $row->id . '_allocatedmarker';
 650                  $o .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $name,
 651                          'value' => $row->allocatedmarker));
 652              }
 653          } else {
 654              $o .= $this->output->container(get_string('markingworkflowstate' . $workflowstate, 'assign'), $workflowstate);
 655          }
 656          return $o;
 657      }
 658  
 659      /**
 660       * For download only - list current marking workflow state
 661       *
 662       * @param stdClass $row - The row of data
 663       * @return string The current marking workflow state
 664       */
 665      public function col_workflowstate($row) {
 666          $state = $row->workflowstate;
 667          if (empty($state)) {
 668              $state = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
 669          }
 670  
 671          return get_string('markingworkflowstate' . $state, 'assign');
 672      }
 673  
 674      /**
 675       * list current marker
 676       *
 677       * @param stdClass $row - The row of data
 678       * @return id the user->id of the marker.
 679       */
 680      public function col_allocatedmarker(stdClass $row) {
 681          static $markers = null;
 682          static $markerlist = array();
 683          if ($markers === null) {
 684              list($sort, $params) = users_order_by_sql('u');
 685              // Only enrolled users could be assigned as potential markers.
 686              $markers = get_enrolled_users($this->assignment->get_context(), 'mod/assign:grade', 0, 'u.*', $sort);
 687              $markerlist[0] = get_string('choosemarker', 'assign');
 688              $viewfullnames = has_capability('moodle/site:viewfullnames', $this->assignment->get_context());
 689              foreach ($markers as $marker) {
 690                  $markerlist[$marker->id] = fullname($marker, $viewfullnames);
 691              }
 692          }
 693          if (empty($markerlist)) {
 694              // TODO: add some form of notification here that no markers are available.
 695              return '';
 696          }
 697          if ($this->is_downloading()) {
 698              if (isset($markers[$row->allocatedmarker])) {
 699                  return fullname($markers[$row->allocatedmarker],
 700                          has_capability('moodle/site:viewfullnames', $this->assignment->get_context()));
 701              } else {
 702                  return '';
 703              }
 704          }
 705  
 706          if ($this->quickgrading && has_capability('mod/assign:manageallocations', $this->assignment->get_context()) &&
 707              (empty($row->workflowstate) ||
 708               $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INMARKING ||
 709               $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED)) {
 710  
 711              $name = 'quickgrade_' . $row->id . '_allocatedmarker';
 712              return  html_writer::select($markerlist, $name, $row->allocatedmarker, false);
 713          } else if (!empty($row->allocatedmarker)) {
 714              $output = '';
 715              if ($this->quickgrading) { // Add hidden field for quickgrading page.
 716                  $name = 'quickgrade_' . $row->id . '_allocatedmarker';
 717                  $attributes = ['type' => 'hidden', 'name' => $name, 'value' => $row->allocatedmarker];
 718                  $output .= html_writer::empty_tag('input', $attributes);
 719              }
 720              $output .= $markerlist[$row->allocatedmarker];
 721              return $output;
 722          }
 723      }
 724      /**
 725       * For download only - list all the valid options for this custom scale.
 726       *
 727       * @param stdClass $row - The row of data
 728       * @return string A list of valid options for the current scale
 729       */
 730      public function col_scale($row) {
 731          global $DB;
 732  
 733          if (empty($this->scale)) {
 734              $dbparams = array('id' => -($this->assignment->get_instance()->grade));
 735              $this->scale = $DB->get_record('scale', $dbparams);
 736          }
 737  
 738          if (!empty($this->scale->scale)) {
 739              return implode("\n", explode(',', $this->scale->scale));
 740          }
 741          return '';
 742      }
 743  
 744      /**
 745       * Display a grade with scales etc.
 746       *
 747       * @param string $grade
 748       * @param boolean $editable
 749       * @param int $userid The user id of the user this grade belongs to
 750       * @param int $modified Timestamp showing when the grade was last modified
 751       * @return string The formatted grade
 752       */
 753      public function display_grade($grade, $editable, $userid, $modified) {
 754          if ($this->is_downloading()) {
 755              if ($this->assignment->get_instance()->grade >= 0) {
 756                  if ($grade == -1 || $grade === null) {
 757                      return '';
 758                  }
 759                  $gradeitem = $this->assignment->get_grade_item();
 760                  return format_float($grade, $gradeitem->get_decimals());
 761              } else {
 762                  // This is a custom scale.
 763                  $scale = $this->assignment->display_grade($grade, false);
 764                  if ($scale == '-') {
 765                      $scale = '';
 766                  }
 767                  return $scale;
 768              }
 769          }
 770          return $this->assignment->display_grade($grade, $editable, $userid, $modified);
 771      }
 772  
 773      /**
 774       * Get the team info for this user.
 775       *
 776       * @param stdClass $row
 777       * @return string The team name
 778       */
 779      public function col_team(stdClass $row) {
 780          $submission = false;
 781          $group = false;
 782          $this->get_group_and_submission($row->id, $group, $submission, -1);
 783          if ($group) {
 784              return $group->name;
 785          } else if ($this->assignment->get_instance()->preventsubmissionnotingroup) {
 786              $usergroups = $this->assignment->get_all_groups($row->id);
 787              if (count($usergroups) > 1) {
 788                  return get_string('multipleteamsgrader', 'assign');
 789              } else {
 790                  return get_string('noteamgrader', 'assign');
 791              }
 792          }
 793          return get_string('defaultteam', 'assign');
 794      }
 795  
 796      /**
 797       * Use a static cache to try and reduce DB calls.
 798       *
 799       * @param int $userid The user id for this submission
 800       * @param int $group The groupid (returned)
 801       * @param stdClass|false $submission The stdClass submission or false (returned)
 802       * @param int $attemptnumber Return a specific attempt number (-1 for latest)
 803       */
 804      protected function get_group_and_submission($userid, &$group, &$submission, $attemptnumber) {
 805          $group = false;
 806          if (isset($this->submissiongroups[$userid])) {
 807              $group = $this->submissiongroups[$userid];
 808          } else {
 809              $group = $this->assignment->get_submission_group($userid, false);
 810              $this->submissiongroups[$userid] = $group;
 811          }
 812  
 813          $groupid = 0;
 814          if ($group) {
 815              $groupid = $group->id;
 816          }
 817  
 818          // Static cache is keyed by groupid and attemptnumber.
 819          // We may need both the latest and previous attempt in the same page.
 820          if (isset($this->groupsubmissions[$groupid . ':' . $attemptnumber])) {
 821              $submission = $this->groupsubmissions[$groupid . ':' . $attemptnumber];
 822          } else {
 823              $submission = $this->assignment->get_group_submission($userid, $groupid, false, $attemptnumber);
 824              $this->groupsubmissions[$groupid . ':' . $attemptnumber] = $submission;
 825          }
 826      }
 827  
 828      /**
 829       * Format a list of outcomes.
 830       *
 831       * @param stdClass $row
 832       * @return string
 833       */
 834      public function col_outcomes(stdClass $row) {
 835          $outcomes = '';
 836          foreach ($this->gradinginfo->outcomes as $index => $outcome) {
 837              $options = make_grades_menu(-$outcome->scaleid);
 838  
 839              $options[0] = get_string('nooutcome', 'grades');
 840              if ($this->quickgrading && !($outcome->grades[$row->userid]->locked)) {
 841                  $select = '<select name="outcome_' . $index . '_' . $row->userid . '" class="quickgrade">';
 842                  foreach ($options as $optionindex => $optionvalue) {
 843                      $selected = '';
 844                      if ($outcome->grades[$row->userid]->grade == $optionindex) {
 845                          $selected = 'selected="selected"';
 846                      }
 847                      $select .= '<option value="' . $optionindex . '"' . $selected . '>' . $optionvalue . '</option>';
 848                  }
 849                  $select .= '</select>';
 850                  $outcomes .= $this->output->container($outcome->name . ': ' . $select, 'outcome');
 851              } else {
 852                  $name = $outcome->name . ': ' . $options[$outcome->grades[$row->userid]->grade];
 853                  if ($this->is_downloading()) {
 854                      $outcomes .= $name;
 855                  } else {
 856                      $outcomes .= $this->output->container($name, 'outcome');
 857                  }
 858              }
 859          }
 860  
 861          return $outcomes;
 862      }
 863  
 864  
 865      /**
 866       * Format a user picture for display.
 867       *
 868       * @param stdClass $row
 869       * @return string
 870       */
 871      public function col_picture(stdClass $row) {
 872          return $this->output->user_picture($row);
 873      }
 874  
 875      /**
 876       * Format a user record for display (link to profile).
 877       *
 878       * @param stdClass $row
 879       * @return string
 880       */
 881      public function col_fullname($row) {
 882          if (!$this->is_downloading()) {
 883              $courseid = $this->assignment->get_course()->id;
 884              $link = new moodle_url('/user/view.php', array('id' => $row->id, 'course' => $courseid));
 885              $fullname = $this->output->action_link($link, $this->assignment->fullname($row));
 886          } else {
 887              $fullname = $this->assignment->fullname($row);
 888          }
 889  
 890          if (!$this->assignment->is_active_user($row->id)) {
 891              $suspendedstring = get_string('userenrolmentsuspended', 'grades');
 892              $fullname .= ' ' . $this->output->pix_icon('i/enrolmentsuspended', $suspendedstring);
 893              $fullname = html_writer::tag('span', $fullname, array('class' => 'usersuspended'));
 894          }
 895          return $fullname;
 896      }
 897  
 898      /**
 899       * Insert a checkbox for selecting the current row for batch operations.
 900       *
 901       * @param stdClass $row
 902       * @return string
 903       */
 904      public function col_select(stdClass $row) {
 905          $selectcol = '<label class="accesshide" for="selectuser_' . $row->userid . '">';
 906          $selectcol .= get_string('selectuser', 'assign', $this->assignment->fullname($row));
 907          $selectcol .= '</label>';
 908          $selectcol .= '<input type="checkbox"
 909                                id="selectuser_' . $row->userid . '"
 910                                name="selectedusers"
 911                                value="' . $row->userid . '"/>';
 912          $selectcol .= '<input type="hidden"
 913                                name="grademodified_' . $row->userid . '"
 914                                value="' . $row->timemarked . '"/>';
 915          $selectcol .= '<input type="hidden"
 916                                name="gradeattempt_' . $row->userid . '"
 917                                value="' . $row->attemptnumber . '"/>';
 918          return $selectcol;
 919      }
 920  
 921      /**
 922       * Return a users grades from the listing of all grade data for this assignment.
 923       *
 924       * @param int $userid
 925       * @return mixed stdClass or false
 926       */
 927      private function get_gradebook_data_for_user($userid) {
 928          if (isset($this->gradinginfo->items[0]) && $this->gradinginfo->items[0]->grades[$userid]) {
 929              return $this->gradinginfo->items[0]->grades[$userid];
 930          }
 931          return false;
 932      }
 933  
 934      /**
 935       * Format a column of data for display.
 936       *
 937       * @param stdClass $row
 938       * @return string
 939       */
 940      public function col_gradecanbechanged(stdClass $row) {
 941          $gradingdisabled = $this->assignment->grading_disabled($row->id);
 942          if ($gradingdisabled) {
 943              return get_string('no');
 944          } else {
 945              return get_string('yes');
 946          }
 947      }
 948  
 949      /**
 950       * Format a column of data for display
 951       *
 952       * @param stdClass $row
 953       * @return string
 954       */
 955      public function col_grademax(stdClass $row) {
 956          if ($this->assignment->get_instance()->grade > 0) {
 957              $gradeitem = $this->assignment->get_grade_item();
 958              return format_float($this->assignment->get_instance()->grade, $gradeitem->get_decimals());
 959          } else {
 960              return '';
 961          }
 962      }
 963  
 964      /**
 965       * Format a column of data for display.
 966       *
 967       * @param stdClass $row
 968       * @return string
 969       */
 970      public function col_grade(stdClass $row) {
 971          $o = '';
 972  
 973          $link = '';
 974          $separator = $this->output->spacer(array(), true);
 975          $grade = '';
 976          $gradingdisabled = $this->assignment->grading_disabled($row->id);
 977  
 978          if (!$this->is_downloading() && $this->hasgrade) {
 979              $urlparams = array('id' => $this->assignment->get_course_module()->id,
 980                                 'rownum' => 0,
 981                                 'action' => 'grader');
 982  
 983              if ($this->assignment->is_blind_marking()) {
 984                  if (empty($row->recordid)) {
 985                      $row->recordid = $this->assignment->get_uniqueid_for_user($row->userid);
 986                  }
 987                  $urlparams['blindid'] = $row->recordid;
 988              } else {
 989                  $urlparams['userid'] = $row->userid;
 990              }
 991  
 992              $url = new moodle_url('/mod/assign/view.php', $urlparams);
 993              $link = '<a href="' . $url . '" class="btn btn-primary">' . get_string('grade') . '</a>';
 994              $grade .= $link . $separator;
 995          }
 996  
 997          $grade .= $this->display_grade($row->grade,
 998                                         $this->quickgrading && !$gradingdisabled,
 999                                         $row->userid,
1000                                         $row->timemarked);
1001  
1002          return $grade;
1003      }
1004  
1005      /**
1006       * Format a column of data for display.
1007       *
1008       * @param stdClass $row
1009       * @return string
1010       */
1011      public function col_finalgrade(stdClass $row) {
1012          $o = '';
1013  
1014          $grade = $this->get_gradebook_data_for_user($row->userid);
1015          if ($grade) {
1016              $o = $this->display_grade($grade->grade, false, $row->userid, $row->timemarked);
1017          }
1018  
1019          return $o;
1020      }
1021  
1022      /**
1023       * Format a column of data for display.
1024       *
1025       * @param stdClass $row
1026       * @return string
1027       */
1028      public function col_timemarked(stdClass $row) {
1029          $o = '-';
1030  
1031          if ($row->timemarked && $row->grade !== null && $row->grade >= 0) {
1032              $o = userdate($row->timemarked);
1033          }
1034          if ($row->timemarked && $this->is_downloading()) {
1035              // Force it for downloads as it affects import.
1036              $o = userdate($row->timemarked);
1037          }
1038  
1039          return $o;
1040      }
1041  
1042      /**
1043       * Format a column of data for display.
1044       *
1045       * @param stdClass $row
1046       * @return string
1047       */
1048      public function col_timesubmitted(stdClass $row) {
1049          $o = '-';
1050  
1051          $group = false;
1052          $submission = false;
1053          $this->get_group_and_submission($row->id, $group, $submission, -1);
1054          if ($submission && $submission->timemodified && $submission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
1055              $o = userdate($submission->timemodified);
1056          } else if ($row->timesubmitted && $row->status != ASSIGN_SUBMISSION_STATUS_NEW) {
1057              $o = userdate($row->timesubmitted);
1058          }
1059  
1060          return $o;
1061      }
1062  
1063      /**
1064       * Format a column of data for display
1065       *
1066       * @param stdClass $row
1067       * @return string
1068       */
1069      public function col_status(stdClass $row) {
1070          $o = '';
1071  
1072          $instance = $this->assignment->get_instance($row->userid);
1073  
1074          $due = $instance->duedate;
1075          if ($row->extensionduedate) {
1076              $due = $row->extensionduedate;
1077          } else if (!empty($row->duedate)) {
1078              // The override due date.
1079              $due = $row->duedate;
1080          }
1081  
1082          $group = false;
1083          $submission = false;
1084  
1085          if ($instance->teamsubmission) {
1086              $this->get_group_and_submission($row->id, $group, $submission, -1);
1087          }
1088  
1089          if ($instance->teamsubmission && !$group && !$instance->preventsubmissionnotingroup) {
1090              $group = true;
1091          }
1092  
1093          if ($group && $submission) {
1094              $timesubmitted = $submission->timemodified;
1095              $status = $submission->status;
1096          } else {
1097              $timesubmitted = $row->timesubmitted;
1098              $status = $row->status;
1099          }
1100  
1101          $displaystatus = $status;
1102          if ($displaystatus == 'new') {
1103              $displaystatus = '';
1104          }
1105  
1106          if ($this->assignment->is_any_submission_plugin_enabled()) {
1107  
1108              $o .= $this->output->container(get_string('submissionstatus_' . $displaystatus, 'assign'),
1109                                             array('class' => 'submissionstatus' .$displaystatus));
1110              if ($due && $timesubmitted > $due && $status != ASSIGN_SUBMISSION_STATUS_NEW) {
1111                  $usertime = format_time($timesubmitted - $due);
1112                  $latemessage = get_string('submittedlateshort',
1113                                            'assign',
1114                                            $usertime);
1115                  $o .= $this->output->container($latemessage, 'latesubmission');
1116              }
1117              if ($row->locked) {
1118                  $lockedstr = get_string('submissionslockedshort', 'assign');
1119                  $o .= $this->output->container($lockedstr, 'lockedsubmission');
1120              }
1121  
1122              // Add status of "grading" if markflow is not enabled.
1123              if (!$instance->markingworkflow) {
1124                  if ($row->grade !== null && $row->grade >= 0) {
1125                      if ($row->timemarked < $row->timesubmitted) {
1126                          $o .= $this->output->container(get_string('gradedfollowupsubmit', 'assign'), 'gradingreminder');
1127                      } else {
1128                          $o .= $this->output->container(get_string('graded', 'assign'), 'submissiongraded');
1129                      }
1130                  } else if (!$timesubmitted || $status == ASSIGN_SUBMISSION_STATUS_NEW) {
1131                      $now = time();
1132                      if ($due && ($now > $due)) {
1133                          $overduestr = get_string('overdue', 'assign', format_time($now - $due));
1134                          $o .= $this->output->container($overduestr, 'overduesubmission');
1135                      }
1136                  }
1137              }
1138          }
1139  
1140          if ($instance->markingworkflow) {
1141              $o .= $this->col_workflowstatus($row);
1142          }
1143          if ($row->extensionduedate) {
1144              $userdate = userdate($row->extensionduedate);
1145              $extensionstr = get_string('userextensiondate', 'assign', $userdate);
1146              $o .= $this->output->container($extensionstr, 'extensiondate');
1147          }
1148  
1149          if ($this->is_downloading()) {
1150              $o = strip_tags(rtrim(str_replace('</div>', ' - ', $o), '- '));
1151          }
1152  
1153          return $o;
1154      }
1155  
1156      /**
1157       * Format a column of data for display.
1158       *
1159       * @param stdClass $row
1160       * @return string
1161       */
1162      public function col_allowsubmissionsfromdate(stdClass $row) {
1163          $o = '';
1164  
1165          if ($row->allowsubmissionsfromdate) {
1166              $userdate = userdate($row->allowsubmissionsfromdate);
1167              $o = ($this->is_downloading()) ? $userdate : $this->output->container($userdate, 'allowsubmissionsfromdate');
1168          }
1169  
1170          return $o;
1171      }
1172  
1173      /**
1174       * Format a column of data for display.
1175       *
1176       * @param stdClass $row
1177       * @return string
1178       */
1179      public function col_duedate(stdClass $row) {
1180          $o = '';
1181  
1182          if ($row->duedate) {
1183              $userdate = userdate($row->duedate);
1184              $o = ($this->is_downloading()) ? $userdate : $this->output->container($userdate, 'duedate');
1185          }
1186  
1187          return $o;
1188      }
1189  
1190      /**
1191       * Format a column of data for display.
1192       *
1193       * @param stdClass $row
1194       * @return string
1195       */
1196      public function col_cutoffdate(stdClass $row) {
1197          $o = '';
1198  
1199          if ($row->cutoffdate) {
1200              $userdate = userdate($row->cutoffdate);
1201              $o = ($this->is_downloading()) ? $userdate : $this->output->container($userdate, 'cutoffdate');
1202          }
1203  
1204          return $o;
1205      }
1206  
1207      /**
1208       * Format a column of data for display.
1209       *
1210       * @param stdClass $row
1211       * @return string
1212       */
1213      public function col_userid(stdClass $row) {
1214          global $USER;
1215  
1216          $edit = '';
1217  
1218          $actions = array();
1219  
1220          $urlparams = array('id' => $this->assignment->get_course_module()->id,
1221                                 'rownum' => 0,
1222                                 'action' => 'grader');
1223  
1224          if ($this->assignment->is_blind_marking()) {
1225              if (empty($row->recordid)) {
1226                  $row->recordid = $this->assignment->get_uniqueid_for_user($row->userid);
1227              }
1228              $urlparams['blindid'] = $row->recordid;
1229          } else {
1230              $urlparams['userid'] = $row->userid;
1231          }
1232          $url = new moodle_url('/mod/assign/view.php', $urlparams);
1233          $noimage = null;
1234  
1235          if (!$row->grade) {
1236              $description = get_string('grade');
1237          } else {
1238              $description = get_string('updategrade', 'assign');
1239          }
1240          $actions['grade'] = new action_menu_link_secondary(
1241              $url,
1242              $noimage,
1243              $description
1244          );
1245  
1246          // Everything we need is in the row.
1247          $submission = $row;
1248          $flags = $row;
1249          if ($this->assignment->get_instance()->teamsubmission) {
1250              // Use the cache for this.
1251              $submission = false;
1252              $group = false;
1253              $this->get_group_and_submission($row->id, $group, $submission, -1);
1254          }
1255  
1256          $submissionsopen = $this->assignment->submissions_open($row->id,
1257                                                                 true,
1258                                                                 $submission,
1259                                                                 $flags,
1260                                                                 $this->gradinginfo);
1261          $caneditsubmission = $this->assignment->can_edit_submission($row->id, $USER->id);
1262  
1263          // Hide for offline assignments.
1264          if ($this->assignment->is_any_submission_plugin_enabled()) {
1265              if (!$row->status ||
1266                      $row->status == ASSIGN_SUBMISSION_STATUS_DRAFT ||
1267                      !$this->assignment->get_instance()->submissiondrafts) {
1268  
1269                  if (!$row->locked) {
1270                      $urlparams = array('id' => $this->assignment->get_course_module()->id,
1271                                         'userid' => $row->id,
1272                                         'action' => 'lock',
1273                                         'sesskey' => sesskey(),
1274                                         'page' => $this->currpage);
1275                      $url = new moodle_url('/mod/assign/view.php', $urlparams);
1276  
1277                      $description = get_string('preventsubmissionsshort', 'assign');
1278                      $actions['lock'] = new action_menu_link_secondary(
1279                          $url,
1280                          $noimage,
1281                          $description
1282                      );
1283                  } else {
1284                      $urlparams = array('id' => $this->assignment->get_course_module()->id,
1285                                         'userid' => $row->id,
1286                                         'action' => 'unlock',
1287                                         'sesskey' => sesskey(),
1288                                         'page' => $this->currpage);
1289                      $url = new moodle_url('/mod/assign/view.php', $urlparams);
1290                      $description = get_string('allowsubmissionsshort', 'assign');
1291                      $actions['unlock'] = new action_menu_link_secondary(
1292                          $url,
1293                          $noimage,
1294                          $description
1295                      );
1296                  }
1297              }
1298  
1299              if ($submissionsopen &&
1300                      $USER->id != $row->id &&
1301                      $caneditsubmission) {
1302                  $urlparams = array('id' => $this->assignment->get_course_module()->id,
1303                                     'userid' => $row->id,
1304                                     'action' => 'editsubmission',
1305                                     'sesskey' => sesskey(),
1306                                     'page' => $this->currpage);
1307                  $url = new moodle_url('/mod/assign/view.php', $urlparams);
1308                  $description = get_string('editsubmission', 'assign');
1309                  $actions['editsubmission'] = new action_menu_link_secondary(
1310                      $url,
1311                      $noimage,
1312                      $description
1313                  );
1314              }
1315              if ($USER->id != $row->id &&
1316                      $caneditsubmission &&
1317                      !empty($row->status)) {
1318                  $urlparams = array('id' => $this->assignment->get_course_module()->id,
1319                                     'userid' => $row->id,
1320                                     'action' => 'removesubmissionconfirm',
1321                                     'sesskey' => sesskey(),
1322                                     'page' => $this->currpage);
1323                  $url = new moodle_url('/mod/assign/view.php', $urlparams);
1324                  $description = get_string('removesubmission', 'assign');
1325                  $actions['removesubmission'] = new action_menu_link_secondary(
1326                      $url,
1327                      $noimage,
1328                      $description
1329                  );
1330              }
1331          }
1332          if (($this->assignment->get_instance()->duedate ||
1333                  $this->assignment->get_instance()->cutoffdate) &&
1334                  $this->hasgrantextension) {
1335               $urlparams = array('id' => $this->assignment->get_course_module()->id,
1336                                  'userid' => $row->id,
1337                                  'action' => 'grantextension',
1338                                  'sesskey' => sesskey(),
1339                                  'page' => $this->currpage);
1340               $url = new moodle_url('/mod/assign/view.php', $urlparams);
1341               $description = get_string('grantextension', 'assign');
1342               $actions['grantextension'] = new action_menu_link_secondary(
1343                   $url,
1344                   $noimage,
1345                   $description
1346               );
1347          }
1348          if ($row->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED &&
1349                  $this->assignment->get_instance()->submissiondrafts) {
1350              $urlparams = array('id' => $this->assignment->get_course_module()->id,
1351                                 'userid' => $row->id,
1352                                 'action' => 'reverttodraft',
1353                                 'sesskey' => sesskey(),
1354                                 'page' => $this->currpage);
1355              $url = new moodle_url('/mod/assign/view.php', $urlparams);
1356              $description = get_string('reverttodraftshort', 'assign');
1357              $actions['reverttodraft'] = new action_menu_link_secondary(
1358                  $url,
1359                  $noimage,
1360                  $description
1361              );
1362          }
1363          if ($row->status == ASSIGN_SUBMISSION_STATUS_DRAFT &&
1364                  $this->assignment->get_instance()->submissiondrafts &&
1365                  $caneditsubmission &&
1366                  $submissionsopen &&
1367                  $row->id != $USER->id) {
1368              $urlparams = array('id' => $this->assignment->get_course_module()->id,
1369                                 'userid' => $row->id,
1370                                 'action' => 'submitotherforgrading',
1371                                 'sesskey' => sesskey(),
1372                                 'page' => $this->currpage);
1373              $url = new moodle_url('/mod/assign/view.php', $urlparams);
1374              $description = get_string('submitforgrading', 'assign');
1375              $actions['submitforgrading'] = new action_menu_link_secondary(
1376                  $url,
1377                  $noimage,
1378                  $description
1379              );
1380          }
1381  
1382          $ismanual = $this->assignment->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL;
1383          $hassubmission = !empty($row->status);
1384          $notreopened = $hassubmission && $row->status != ASSIGN_SUBMISSION_STATUS_REOPENED;
1385          $isunlimited = $this->assignment->get_instance()->maxattempts == ASSIGN_UNLIMITED_ATTEMPTS;
1386          $hasattempts = $isunlimited || $row->attemptnumber < $this->assignment->get_instance()->maxattempts - 1;
1387  
1388          if ($ismanual && $hassubmission && $notreopened && $hasattempts) {
1389              $urlparams = array('id' => $this->assignment->get_course_module()->id,
1390                                 'userid' => $row->id,
1391                                 'action' => 'addattempt',
1392                                 'sesskey' => sesskey(),
1393                                 'page' => $this->currpage);
1394              $url = new moodle_url('/mod/assign/view.php', $urlparams);
1395              $description = get_string('addattempt', 'assign');
1396              $actions['addattempt'] = new action_menu_link_secondary(
1397                  $url,
1398                  $noimage,
1399                  $description
1400              );
1401          }
1402  
1403          $menu = new action_menu();
1404          $menu->set_owner_selector('.gradingtable-actionmenu');
1405          $menu->set_alignment(action_menu::TL, action_menu::BL);
1406          $menu->set_constraint('.gradingtable > .no-overflow');
1407          $menu->set_menu_trigger(get_string('edit'));
1408          foreach ($actions as $action) {
1409              $menu->add($action);
1410          }
1411  
1412          // Prioritise the menu ahead of all other actions.
1413          $menu->prioritise = true;
1414  
1415          $edit .= $this->output->render($menu);
1416  
1417          return $edit;
1418      }
1419  
1420      /**
1421       * Write the plugin summary with an optional link to view the full feedback/submission.
1422       *
1423       * @param assign_plugin $plugin Submission plugin or feedback plugin
1424       * @param stdClass $item Submission or grade
1425       * @param string $returnaction The return action to pass to the
1426       *                             view_submission page (the current page)
1427       * @param string $returnparams The return params to pass to the view_submission
1428       *                             page (the current page)
1429       * @return string The summary with an optional link
1430       */
1431      private function format_plugin_summary_with_link(assign_plugin $plugin,
1432                                                       stdClass $item,
1433                                                       $returnaction,
1434                                                       $returnparams) {
1435          $link = '';
1436          $showviewlink = false;
1437  
1438          $summary = $plugin->view_summary($item, $showviewlink);
1439          $separator = '';
1440          if ($showviewlink) {
1441              $viewstr = get_string('view' . substr($plugin->get_subtype(), strlen('assign')), 'assign');
1442              $icon = $this->output->pix_icon('t/preview', $viewstr);
1443              $urlparams = array('id' => $this->assignment->get_course_module()->id,
1444                                                       'sid' => $item->id,
1445                                                       'gid' => $item->id,
1446                                                       'plugin' => $plugin->get_type(),
1447                                                       'action' => 'viewplugin' . $plugin->get_subtype(),
1448                                                       'returnaction' => $returnaction,
1449                                                       'returnparams' => http_build_query($returnparams));
1450              $url = new moodle_url('/mod/assign/view.php', $urlparams);
1451              $link = $this->output->action_link($url, $icon);
1452              $separator = $this->output->spacer(array(), true);
1453          }
1454  
1455          return $link . $separator . $summary;
1456      }
1457  
1458  
1459      /**
1460       * Format the submission and feedback columns.
1461       *
1462       * @param string $colname The column name
1463       * @param stdClass $row The submission row
1464       * @return mixed string or NULL
1465       */
1466      public function other_cols($colname, $row) {
1467          // For extra user fields the result is already in $row.
1468          if (empty($this->plugincache[$colname])) {
1469              return parent::other_cols($colname, $row);
1470          }
1471  
1472          // This must be a plugin field.
1473          $plugincache = $this->plugincache[$colname];
1474  
1475          $plugin = $plugincache[0];
1476  
1477          $field = null;
1478          if (isset($plugincache[1])) {
1479              $field = $plugincache[1];
1480          }
1481  
1482          if ($plugin->is_visible() && $plugin->is_enabled()) {
1483              if ($plugin->get_subtype() == 'assignsubmission') {
1484                  if ($this->assignment->get_instance()->teamsubmission) {
1485                      $group = false;
1486                      $submission = false;
1487  
1488                      $this->get_group_and_submission($row->id, $group, $submission, -1);
1489                      if ($submission) {
1490                          if ($submission->status == ASSIGN_SUBMISSION_STATUS_REOPENED) {
1491                              // For a newly reopened submission - we want to show the previous submission in the table.
1492                              $this->get_group_and_submission($row->id, $group, $submission, $submission->attemptnumber-1);
1493                          }
1494                          if (isset($field)) {
1495                              return $plugin->get_editor_text($field, $submission->id);
1496                          }
1497                          return $this->format_plugin_summary_with_link($plugin,
1498                                                                        $submission,
1499                                                                        'grading',
1500                                                                        array());
1501                      }
1502                  } else if ($row->submissionid) {
1503                      if ($row->status == ASSIGN_SUBMISSION_STATUS_REOPENED) {
1504                          // For a newly reopened submission - we want to show the previous submission in the table.
1505                          $submission = $this->assignment->get_user_submission($row->userid, false, $row->attemptnumber - 1);
1506                      } else {
1507                          $submission = new stdClass();
1508                          $submission->id = $row->submissionid;
1509                          $submission->timecreated = $row->firstsubmission;
1510                          $submission->timemodified = $row->timesubmitted;
1511                          $submission->assignment = $this->assignment->get_instance()->id;
1512                          $submission->userid = $row->userid;
1513                          $submission->attemptnumber = $row->attemptnumber;
1514                      }
1515                      // Field is used for only for import/export and refers the the fieldname for the text editor.
1516                      if (isset($field)) {
1517                          return $plugin->get_editor_text($field, $submission->id);
1518                      }
1519                      return $this->format_plugin_summary_with_link($plugin,
1520                                                                    $submission,
1521                                                                    'grading',
1522                                                                    array());
1523                  }
1524              } else {
1525                  $grade = null;
1526                  if (isset($field)) {
1527                      return $plugin->get_editor_text($field, $row->gradeid);
1528                  }
1529  
1530                  if ($row->gradeid) {
1531                      $grade = new stdClass();
1532                      $grade->id = $row->gradeid;
1533                      $grade->timecreated = $row->firstmarked;
1534                      $grade->timemodified = $row->timemarked;
1535                      $grade->assignment = $this->assignment->get_instance()->id;
1536                      $grade->userid = $row->userid;
1537                      $grade->grade = $row->grade;
1538                      $grade->mailed = $row->mailed;
1539                      $grade->attemptnumber = $row->attemptnumber;
1540                  }
1541                  if ($this->quickgrading && $plugin->supports_quickgrading()) {
1542                      return $plugin->get_quickgrading_html($row->userid, $grade);
1543                  } else if ($grade) {
1544                      return $this->format_plugin_summary_with_link($plugin,
1545                                                                    $grade,
1546                                                                    'grading',
1547                                                                    array());
1548                  }
1549              }
1550          }
1551          return '';
1552      }
1553  
1554      /**
1555       * Using the current filtering and sorting - load all rows and return a single column from them.
1556       *
1557       * @param string $columnname The name of the raw column data
1558       * @return array of data
1559       */
1560      public function get_column_data($columnname) {
1561          $this->setup();
1562          $this->currpage = 0;
1563          $this->query_db($this->tablemaxrows);
1564          $result = array();
1565          foreach ($this->rawdata as $row) {
1566              $result[] = $row->$columnname;
1567          }
1568          return $result;
1569      }
1570  
1571      /**
1572       * Return things to the renderer.
1573       *
1574       * @return string the assignment name
1575       */
1576      public function get_assignment_name() {
1577          return $this->assignment->get_instance()->name;
1578      }
1579  
1580      /**
1581       * Return things to the renderer.
1582       *
1583       * @return int the course module id
1584       */
1585      public function get_course_module_id() {
1586          return $this->assignment->get_course_module()->id;
1587      }
1588  
1589      /**
1590       * Return things to the renderer.
1591       *
1592       * @return int the course id
1593       */
1594      public function get_course_id() {
1595          return $this->assignment->get_course()->id;
1596      }
1597  
1598      /**
1599       * Return things to the renderer.
1600       *
1601       * @return stdClass The course context
1602       */
1603      public function get_course_context() {
1604          return $this->assignment->get_course_context();
1605      }
1606  
1607      /**
1608       * Return things to the renderer.
1609       *
1610       * @return bool Does this assignment accept submissions
1611       */
1612      public function submissions_enabled() {
1613          return $this->assignment->is_any_submission_plugin_enabled();
1614      }
1615  
1616      /**
1617       * Return things to the renderer.
1618       *
1619       * @return bool Can this user view all grades (the gradebook)
1620       */
1621      public function can_view_all_grades() {
1622          $context = $this->assignment->get_course_context();
1623          return has_capability('gradereport/grader:view', $context) &&
1624                 has_capability('moodle/grade:viewall', $context);
1625      }
1626  
1627      /**
1628       * Always return a valid sort - even if the userid column is missing.
1629       * @return array column name => SORT_... constant.
1630       */
1631      public function get_sort_columns() {
1632          $result = parent::get_sort_columns();
1633  
1634          $assignment = $this->assignment->get_instance();
1635          if (empty($assignment->blindmarking)) {
1636              $result = array_merge($result, array('userid' => SORT_ASC));
1637          } else {
1638              $result = array_merge($result, [
1639                      'COALESCE(s.timecreated, '  . time()        . ')'   => SORT_ASC,
1640                      'COALESCE(s.id, '           . PHP_INT_MAX   . ')'   => SORT_ASC,
1641                      'um.id'                                             => SORT_ASC,
1642                  ]);
1643          }
1644          return $result;
1645      }
1646  
1647      /**
1648       * Override the table show_hide_link to not show for select column.
1649       *
1650       * @param string $column the column name, index into various names.
1651       * @param int $index numerical index of the column.
1652       * @return string HTML fragment.
1653       */
1654      protected function show_hide_link($column, $index) {
1655          if ($index > 0 || !$this->hasgrade) {
1656              return parent::show_hide_link($column, $index);
1657          }
1658          return '';
1659      }
1660  
1661      /**
1662       * Overides setup to ensure it will only run a single time.
1663       */
1664      public function setup() {
1665          // Check if the setup function has been called before, we should not run it twice.
1666          // If we do the sortorder of the table will be broken.
1667          if (!empty($this->setup)) {
1668              return;
1669          }
1670          parent::setup();
1671      }
1672  }