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