[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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->assignment = $assignment; 87 88 // Check permissions up front. 89 $this->hasgrantextension = has_capability('mod/assign:grantextension', 90 $this->assignment->get_context()); 91 $this->hasgrade = $this->assignment->can_grade(); 92 93 // Check if we have the elevated view capablities to see the blind details. 94 $this->hasviewblind = has_capability('mod/assign:viewblinddetails', 95 $this->assignment->get_context()); 96 97 foreach ($assignment->get_feedback_plugins() as $plugin) { 98 if ($plugin->is_visible() && $plugin->is_enabled()) { 99 foreach ($plugin->get_grading_batch_operations() as $action => $description) { 100 if (empty($this->plugingradingbatchoperations)) { 101 $this->plugingradingbatchoperations[$plugin->get_type()] = array(); 102 } 103 $this->plugingradingbatchoperations[$plugin->get_type()][$action] = $description; 104 } 105 } 106 } 107 $this->perpage = $perpage; 108 $this->quickgrading = $quickgrading && $this->hasgrade; 109 $this->output = $PAGE->get_renderer('mod_assign'); 110 111 $urlparams = array('action'=>'grading', 'id'=>$assignment->get_course_module()->id); 112 $url = new moodle_url($CFG->wwwroot . '/mod/assign/view.php', $urlparams); 113 $this->define_baseurl($url); 114 115 // Do some business - then set the sql. 116 $currentgroup = groups_get_activity_group($assignment->get_course_module(), true); 117 118 if ($rowoffset) { 119 $this->rownum = $rowoffset - 1; 120 } 121 122 $users = array_keys( $assignment->list_participants($currentgroup, true)); 123 if (count($users) == 0) { 124 // Insert a record that will never match to the sql is still valid. 125 $users[] = -1; 126 } 127 128 $params = array(); 129 $params['assignmentid1'] = (int)$this->assignment->get_instance()->id; 130 $params['assignmentid2'] = (int)$this->assignment->get_instance()->id; 131 $params['assignmentid3'] = (int)$this->assignment->get_instance()->id; 132 133 $extrauserfields = get_extra_user_fields($this->assignment->get_context()); 134 135 $fields = user_picture::fields('u', $extrauserfields) . ', '; 136 $fields .= 'u.id as userid, '; 137 $fields .= 's.status as status, '; 138 $fields .= 's.id as submissionid, '; 139 $fields .= 's.timecreated as firstsubmission, '; 140 $fields .= 's.timemodified as timesubmitted, '; 141 $fields .= 's.attemptnumber as attemptnumber, '; 142 $fields .= 'g.id as gradeid, '; 143 $fields .= 'g.grade as grade, '; 144 $fields .= 'g.timemodified as timemarked, '; 145 $fields .= 'g.timecreated as firstmarked, '; 146 $fields .= 'uf.mailed as mailed, '; 147 $fields .= 'uf.locked as locked, '; 148 $fields .= 'uf.extensionduedate as extensionduedate, '; 149 $fields .= 'uf.workflowstate as workflowstate, '; 150 $fields .= 'uf.allocatedmarker as allocatedmarker '; 151 152 $from = '{user} u 153 LEFT JOIN {assign_submission} s ON 154 u.id = s.userid AND 155 s.assignment = :assignmentid1 AND 156 s.latest = 1 157 LEFT JOIN {assign_grades} g ON 158 u.id = g.userid AND 159 g.assignment = :assignmentid2 AND 160 g.attemptnumber = s.attemptnumber 161 LEFT JOIN {assign_user_flags} uf ON u.id = uf.userid AND uf.assignment = :assignmentid3'; 162 163 $userparams = array(); 164 $userindex = 0; 165 166 list($userwhere, $userparams) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'user'); 167 $where = 'u.id ' . $userwhere; 168 $params = array_merge($params, $userparams); 169 170 // The filters do not make sense when there are no submissions, so do not apply them. 171 if ($this->assignment->is_any_submission_plugin_enabled()) { 172 if ($filter == ASSIGN_FILTER_SUBMITTED) { 173 $where .= ' AND (s.timemodified IS NOT NULL AND 174 s.status = :submitted) '; 175 $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 176 177 } else if ($filter == ASSIGN_FILTER_NOT_SUBMITTED) { 178 $where .= ' AND (s.timemodified IS NULL OR s.status != :submitted) '; 179 $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 180 } else if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) { 181 $where .= ' AND (s.timemodified IS NOT NULL AND 182 s.status = :submitted AND 183 (s.timemodified > g.timemodified OR g.timemodified IS NULL))'; 184 $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 185 186 } else if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) { 187 $userfilter = (int) array_pop(explode('=', $filter)); 188 $where .= ' AND (u.id = :userid)'; 189 $params['userid'] = $userfilter; 190 } 191 } 192 193 if ($this->assignment->get_instance()->markingworkflow && 194 $this->assignment->get_instance()->markingallocation) { 195 if (has_capability('mod/assign:manageallocations', $this->assignment->get_context())) { 196 // Check to see if marker filter is set. 197 $markerfilter = (int)get_user_preferences('assign_markerfilter', ''); 198 if (!empty($markerfilter)) { 199 if ($markerfilter == ASSIGN_MARKER_FILTER_NO_MARKER) { 200 $where .= ' AND (uf.allocatedmarker IS NULL OR uf.allocatedmarker = 0)'; 201 } else { 202 $where .= ' AND uf.allocatedmarker = :markerid'; 203 $params['markerid'] = $markerfilter; 204 } 205 } 206 } else { // Only show users allocated to this marker. 207 $where .= ' AND uf.allocatedmarker = :markerid'; 208 $params['markerid'] = $USER->id; 209 } 210 } 211 212 if ($this->assignment->get_instance()->markingworkflow) { 213 $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user(); 214 if (!empty($workflowstates)) { 215 $workflowfilter = get_user_preferences('assign_workflowfilter', ''); 216 if ($workflowfilter == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED) { 217 $where .= ' AND (uf.workflowstate = :workflowstate OR uf.workflowstate IS NULL OR '. 218 $DB->sql_isempty('assign_user_flags', 'workflowstate', true, true).')'; 219 $params['workflowstate'] = $workflowfilter; 220 } else if (array_key_exists($workflowfilter, $workflowstates)) { 221 $where .= ' AND uf.workflowstate = :workflowstate'; 222 $params['workflowstate'] = $workflowfilter; 223 } 224 } 225 } 226 227 $this->set_sql($fields, $from, $where, $params); 228 229 if ($downloadfilename) { 230 $this->is_downloading('csv', $downloadfilename); 231 } 232 233 $columns = array(); 234 $headers = array(); 235 236 // Select. 237 if (!$this->is_downloading() && $this->hasgrade) { 238 $columns[] = 'select'; 239 $headers[] = get_string('select') . 240 '<div class="selectall"><label class="accesshide" for="selectall">' . get_string('selectall') . '</label> 241 <input type="checkbox" id="selectall" name="selectall" title="' . get_string('selectall') . '"/></div>'; 242 } 243 244 // User picture. 245 if ($this->hasviewblind || !$this->assignment->is_blind_marking()) { 246 if (!$this->is_downloading()) { 247 $columns[] = 'picture'; 248 $headers[] = get_string('pictureofuser'); 249 } else { 250 $columns[] = 'recordid'; 251 $headers[] = get_string('recordid', 'assign'); 252 } 253 254 // Fullname. 255 $columns[] = 'fullname'; 256 $headers[] = get_string('fullname'); 257 258 foreach ($extrauserfields as $extrafield) { 259 $columns[] = $extrafield; 260 $headers[] = get_user_field_name($extrafield); 261 } 262 } else { 263 // Record ID. 264 $columns[] = 'recordid'; 265 $headers[] = get_string('recordid', 'assign'); 266 } 267 268 // Submission status. 269 if ($assignment->is_any_submission_plugin_enabled()) { 270 $columns[] = 'status'; 271 $headers[] = get_string('status', 'assign'); 272 } else if ($this->assignment->get_instance()->markingworkflow) { 273 $columns[] = 'workflowstatus'; 274 $headers[] = get_string('status', 'assign'); 275 } 276 277 // Team submission columns. 278 if ($assignment->get_instance()->teamsubmission) { 279 $columns[] = 'team'; 280 $headers[] = get_string('submissionteam', 'assign'); 281 } 282 // Allocated marker. 283 if ($this->assignment->get_instance()->markingworkflow && 284 $this->assignment->get_instance()->markingallocation && 285 has_capability('mod/assign:manageallocations', $this->assignment->get_context())) { 286 // Add a column for the allocated marker. 287 $columns[] = 'allocatedmarker'; 288 $headers[] = get_string('marker', 'assign'); 289 } 290 // Grade. 291 $columns[] = 'grade'; 292 $headers[] = get_string('grade'); 293 if ($this->is_downloading()) { 294 if ($this->assignment->get_instance()->grade >= 0) { 295 $columns[] = 'grademax'; 296 $headers[] = get_string('maxgrade', 'assign'); 297 } else { 298 // This is a custom scale. 299 $columns[] = 'scale'; 300 $headers[] = get_string('scale', 'assign'); 301 } 302 303 if ($this->assignment->get_instance()->markingworkflow) { 304 // Add a column for the marking workflow state. 305 $columns[] = 'workflowstate'; 306 $headers[] = get_string('markingworkflowstate', 'assign'); 307 } 308 // Add a column for the list of valid marking workflow states. 309 $columns[] = 'gradecanbechanged'; 310 $headers[] = get_string('gradecanbechanged', 'assign'); 311 } 312 if (!$this->is_downloading() && $this->hasgrade) { 313 // We have to call this column userid so we can use userid as a default sortable column. 314 $columns[] = 'userid'; 315 $headers[] = get_string('edit'); 316 } 317 318 // Submission plugins. 319 if ($assignment->is_any_submission_plugin_enabled()) { 320 $columns[] = 'timesubmitted'; 321 $headers[] = get_string('lastmodifiedsubmission', 'assign'); 322 323 foreach ($this->assignment->get_submission_plugins() as $plugin) { 324 if ($this->is_downloading()) { 325 if ($plugin->is_visible() && $plugin->is_enabled()) { 326 foreach ($plugin->get_editor_fields() as $field => $description) { 327 $index = 'plugin' . count($this->plugincache); 328 $this->plugincache[$index] = array($plugin, $field); 329 $columns[] = $index; 330 $headers[] = $plugin->get_name(); 331 } 332 } 333 } else { 334 if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) { 335 $index = 'plugin' . count($this->plugincache); 336 $this->plugincache[$index] = array($plugin); 337 $columns[] = $index; 338 $headers[] = $plugin->get_name(); 339 } 340 } 341 } 342 } 343 344 // Time marked. 345 $columns[] = 'timemarked'; 346 $headers[] = get_string('lastmodifiedgrade', 'assign'); 347 348 // Feedback plugins. 349 foreach ($this->assignment->get_feedback_plugins() as $plugin) { 350 if ($this->is_downloading()) { 351 if ($plugin->is_visible() && $plugin->is_enabled()) { 352 foreach ($plugin->get_editor_fields() as $field => $description) { 353 $index = 'plugin' . count($this->plugincache); 354 $this->plugincache[$index] = array($plugin, $field); 355 $columns[] = $index; 356 $headers[] = $description; 357 } 358 } 359 } else if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) { 360 $index = 'plugin' . count($this->plugincache); 361 $this->plugincache[$index] = array($plugin); 362 $columns[] = $index; 363 $headers[] = $plugin->get_name(); 364 } 365 } 366 367 // Exclude 'Final grade' column in downloaded grading worksheets. 368 if (!$this->is_downloading()) { 369 // Final grade. 370 $columns[] = 'finalgrade'; 371 $headers[] = get_string('finalgrade', 'grades'); 372 } 373 374 // Load the grading info for all users. 375 $this->gradinginfo = grade_get_grades($this->assignment->get_course()->id, 376 'mod', 377 'assign', 378 $this->assignment->get_instance()->id, 379 $users); 380 381 if (!empty($CFG->enableoutcomes) && !empty($this->gradinginfo->outcomes)) { 382 $columns[] = 'outcomes'; 383 $headers[] = get_string('outcomes', 'grades'); 384 } 385 386 // Set the columns. 387 $this->define_columns($columns); 388 $this->define_headers($headers); 389 foreach ($extrauserfields as $extrafield) { 390 $this->column_class($extrafield, $extrafield); 391 } 392 // We require at least one unique column for the sort. 393 $this->sortable(true, 'userid'); 394 $this->no_sorting('recordid'); 395 $this->no_sorting('finalgrade'); 396 $this->no_sorting('userid'); 397 $this->no_sorting('select'); 398 $this->no_sorting('outcomes'); 399 400 if ($assignment->get_instance()->teamsubmission) { 401 $this->no_sorting('team'); 402 } 403 404 $plugincolumnindex = 0; 405 foreach ($this->assignment->get_submission_plugins() as $plugin) { 406 if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) { 407 $submissionpluginindex = 'plugin' . $plugincolumnindex++; 408 $this->no_sorting($submissionpluginindex); 409 } 410 } 411 foreach ($this->assignment->get_feedback_plugins() as $plugin) { 412 if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) { 413 $feedbackpluginindex = 'plugin' . $plugincolumnindex++; 414 $this->no_sorting($feedbackpluginindex); 415 } 416 } 417 418 // When there is no data we still want the column headers printed in the csv file. 419 if ($this->is_downloading()) { 420 $this->start_output(); 421 } 422 } 423 424 /** 425 * Before adding each row to the table make sure rownum is incremented. 426 * 427 * @param array $row row of data from db used to make one row of the table. 428 * @return array one row for the table 429 */ 430 public function format_row($row) { 431 if ($this->rownum < 0) { 432 $this->rownum = $this->currpage * $this->pagesize; 433 } else { 434 $this->rownum += 1; 435 } 436 437 return parent::format_row($row); 438 } 439 440 /** 441 * Add a column with an ID that uniquely identifies this user in this assignment. 442 * 443 * @param stdClass $row 444 * @return string 445 */ 446 public function col_recordid(stdClass $row) { 447 return get_string('hiddenuser', 'assign') . 448 $this->assignment->get_uniqueid_for_user($row->userid); 449 } 450 451 452 /** 453 * Add the userid to the row class so it can be updated via ajax. 454 * 455 * @param stdClass $row The row of data 456 * @return string The row class 457 */ 458 public function get_row_class($row) { 459 return 'user' . $row->userid; 460 } 461 462 /** 463 * Return the number of rows to display on a single page. 464 * 465 * @return int The number of rows per page 466 */ 467 public function get_rows_per_page() { 468 return $this->perpage; 469 } 470 471 /** 472 * list current marking workflow state 473 * 474 * @param stdClass $row 475 * @return string 476 */ 477 public function col_workflowstatus(stdClass $row) { 478 $o = ''; 479 480 $gradingdisabled = $this->assignment->grading_disabled($row->id); 481 // The function in the assignment keeps a static cache of this list of states. 482 $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user(); 483 $workflowstate = $row->workflowstate; 484 if (empty($workflowstate)) { 485 $workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED; 486 } 487 if ($this->quickgrading && !$gradingdisabled) { 488 $notmarked = get_string('markingworkflowstatenotmarked', 'assign'); 489 $name = 'quickgrade_' . $row->id . '_workflowstate'; 490 $o .= html_writer::select($workflowstates, $name, $workflowstate, array('' => $notmarked)); 491 // Check if this user is a marker that can't manage allocations and doesn't have the marker column added. 492 if ($this->assignment->get_instance()->markingworkflow && 493 $this->assignment->get_instance()->markingallocation && 494 !has_capability('mod/assign:manageallocations', $this->assignment->get_context())) { 495 496 $name = 'quickgrade_' . $row->id . '_allocatedmarker'; 497 $o .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $name, 498 'value' => $row->allocatedmarker)); 499 } 500 } else { 501 $o .= $this->output->container(get_string('markingworkflowstate' . $workflowstate, 'assign'), $workflowstate); 502 } 503 return $o; 504 } 505 506 /** 507 * For download only - list current marking workflow state 508 * 509 * @param stdClass $row - The row of data 510 * @return string The current marking workflow state 511 */ 512 public function col_workflowstate($row) { 513 $state = $row->workflowstate; 514 if (empty($state)) { 515 $state = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED; 516 } 517 518 return get_string('markingworkflowstate' . $state, 'assign'); 519 } 520 521 /** 522 * list current marker 523 * 524 * @param stdClass $row - The row of data 525 * @return id the user->id of the marker. 526 */ 527 public function col_allocatedmarker(stdClass $row) { 528 static $markers = null; 529 static $markerlist = array(); 530 if ($markers === null) { 531 $markers = get_users_by_capability($this->assignment->get_context(), 'mod/assign:grade'); 532 $markerlist[0] = get_string('choosemarker', 'assign'); 533 foreach ($markers as $marker) { 534 $markerlist[$marker->id] = fullname($marker); 535 } 536 } 537 if (empty($markerlist)) { 538 // TODO: add some form of notification here that no markers are available. 539 return ''; 540 } 541 if ($this->is_downloading()) { 542 if (isset($markers[$row->allocatedmarker])) { 543 return fullname($markers[$row->allocatedmarker]); 544 } else { 545 return ''; 546 } 547 } 548 549 if ($this->quickgrading && has_capability('mod/assign:manageallocations', $this->assignment->get_context()) && 550 (empty($row->workflowstate) || 551 $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INMARKING || 552 $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED)) { 553 554 $name = 'quickgrade_' . $row->id . '_allocatedmarker'; 555 return html_writer::select($markerlist, $name, $row->allocatedmarker, false); 556 } else if (!empty($row->allocatedmarker)) { 557 $output = ''; 558 if ($this->quickgrading) { // Add hidden field for quickgrading page. 559 $name = 'quickgrade_' . $row->id . '_allocatedmarker'; 560 $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$row->allocatedmarker)); 561 } 562 $output .= $markerlist[$row->allocatedmarker]; 563 return $output; 564 } 565 } 566 /** 567 * For download only - list all the valid options for this custom scale. 568 * 569 * @param stdClass $row - The row of data 570 * @return string A list of valid options for the current scale 571 */ 572 public function col_scale($row) { 573 global $DB; 574 575 if (empty($this->scale)) { 576 $dbparams = array('id'=>-($this->assignment->get_instance()->grade)); 577 $this->scale = $DB->get_record('scale', $dbparams); 578 } 579 580 if (!empty($this->scale->scale)) { 581 return implode("\n", explode(',', $this->scale->scale)); 582 } 583 return ''; 584 } 585 586 /** 587 * Display a grade with scales etc. 588 * 589 * @param string $grade 590 * @param boolean $editable 591 * @param int $userid The user id of the user this grade belongs to 592 * @param int $modified Timestamp showing when the grade was last modified 593 * @return string The formatted grade 594 */ 595 public function display_grade($grade, $editable, $userid, $modified) { 596 if ($this->is_downloading()) { 597 if ($this->assignment->get_instance()->grade >= 0) { 598 if ($grade == -1 || $grade === null) { 599 return ''; 600 } 601 return format_float($grade, 2); 602 } else { 603 // This is a custom scale. 604 $scale = $this->assignment->display_grade($grade, false); 605 if ($scale == '-') { 606 $scale = ''; 607 } 608 return $scale; 609 } 610 } 611 return $this->assignment->display_grade($grade, $editable, $userid, $modified); 612 } 613 614 /** 615 * Get the team info for this user. 616 * 617 * @param stdClass $row 618 * @return string The team name 619 */ 620 public function col_team(stdClass $row) { 621 $submission = false; 622 $group = false; 623 $this->get_group_and_submission($row->id, $group, $submission, -1); 624 if ($group) { 625 return $group->name; 626 } 627 return get_string('defaultteam', 'assign'); 628 } 629 630 /** 631 * Use a static cache to try and reduce DB calls. 632 * 633 * @param int $userid The user id for this submission 634 * @param int $group The groupid (returned) 635 * @param stdClass|false $submission The stdClass submission or false (returned) 636 * @param int $attemptnumber Return a specific attempt number (-1 for latest) 637 */ 638 protected function get_group_and_submission($userid, &$group, &$submission, $attemptnumber) { 639 $group = false; 640 if (isset($this->submissiongroups[$userid])) { 641 $group = $this->submissiongroups[$userid]; 642 } else { 643 $group = $this->assignment->get_submission_group($userid, false); 644 $this->submissiongroups[$userid] = $group; 645 } 646 647 $groupid = 0; 648 if ($group) { 649 $groupid = $group->id; 650 } 651 652 // Static cache is keyed by groupid and attemptnumber. 653 // We may need both the latest and previous attempt in the same page. 654 if (isset($this->groupsubmissions[$groupid . ':' . $attemptnumber])) { 655 $submission = $this->groupsubmissions[$groupid . ':' . $attemptnumber]; 656 } else { 657 $submission = $this->assignment->get_group_submission($userid, $groupid, false, $attemptnumber); 658 $this->groupsubmissions[$groupid . ':' . $attemptnumber] = $submission; 659 } 660 } 661 662 /** 663 * Format a list of outcomes. 664 * 665 * @param stdClass $row 666 * @return string 667 */ 668 public function col_outcomes(stdClass $row) { 669 $outcomes = ''; 670 foreach ($this->gradinginfo->outcomes as $index => $outcome) { 671 $options = make_grades_menu(-$outcome->scaleid); 672 673 $options[0] = get_string('nooutcome', 'grades'); 674 if ($this->quickgrading && !($outcome->grades[$row->userid]->locked)) { 675 $select = '<select name="outcome_' . $index . '_' . $row->userid . '" class="quickgrade">'; 676 foreach ($options as $optionindex => $optionvalue) { 677 $selected = ''; 678 if ($outcome->grades[$row->userid]->grade == $optionindex) { 679 $selected = 'selected="selected"'; 680 } 681 $select .= '<option value="' . $optionindex . '"' . $selected . '>' . $optionvalue . '</option>'; 682 } 683 $select .= '</select>'; 684 $outcomes .= $this->output->container($outcome->name . ': ' . $select, 'outcome'); 685 } else { 686 $name = $outcome->name . ': ' . $options[$outcome->grades[$row->userid]->grade]; 687 if ($this->is_downloading()) { 688 $outcomes .= $name; 689 } else { 690 $outcomes .= $this->output->container($name, 'outcome'); 691 } 692 } 693 } 694 695 return $outcomes; 696 } 697 698 699 /** 700 * Format a user picture for display. 701 * 702 * @param stdClass $row 703 * @return string 704 */ 705 public function col_picture(stdClass $row) { 706 if ($row->picture) { 707 return $this->output->user_picture($row); 708 } 709 return ''; 710 } 711 712 /** 713 * Format a user record for display (link to profile). 714 * 715 * @param stdClass $row 716 * @return string 717 */ 718 public function col_fullname($row) { 719 if (!$this->is_downloading()) { 720 $courseid = $this->assignment->get_course()->id; 721 $link= new moodle_url('/user/view.php', array('id' =>$row->id, 'course'=>$courseid)); 722 $fullname = $this->output->action_link($link, $this->assignment->fullname($row)); 723 } else { 724 $fullname = $this->assignment->fullname($row); 725 } 726 727 if (!$this->assignment->is_active_user($row->id)) { 728 $suspendedstring = get_string('userenrolmentsuspended', 'grades'); 729 $fullname .= ' ' . html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/enrolmentsuspended'), 730 'title' => $suspendedstring, 'alt' => $suspendedstring, 'class' => 'usersuspendedicon')); 731 $fullname = html_writer::tag('span', $fullname, array('class' => 'usersuspended')); 732 } 733 return $fullname; 734 } 735 736 /** 737 * Insert a checkbox for selecting the current row for batch operations. 738 * 739 * @param stdClass $row 740 * @return string 741 */ 742 public function col_select(stdClass $row) { 743 $selectcol = '<label class="accesshide" for="selectuser_' . $row->userid . '">'; 744 $selectcol .= get_string('selectuser', 'assign', $this->assignment->fullname($row)); 745 $selectcol .= '</label>'; 746 $selectcol .= '<input type="checkbox" 747 id="selectuser_' . $row->userid . '" 748 name="selectedusers" 749 value="' . $row->userid . '"/>'; 750 $selectcol .= '<input type="hidden" 751 name="grademodified_' . $row->userid . '" 752 value="' . $row->timemarked . '"/>'; 753 return $selectcol; 754 } 755 756 /** 757 * Return a users grades from the listing of all grade data for this assignment. 758 * 759 * @param int $userid 760 * @return mixed stdClass or false 761 */ 762 private function get_gradebook_data_for_user($userid) { 763 if (isset($this->gradinginfo->items[0]) && $this->gradinginfo->items[0]->grades[$userid]) { 764 return $this->gradinginfo->items[0]->grades[$userid]; 765 } 766 return false; 767 } 768 769 /** 770 * Format a column of data for display. 771 * 772 * @param stdClass $row 773 * @return string 774 */ 775 public function col_gradecanbechanged(stdClass $row) { 776 $gradingdisabled = $this->assignment->grading_disabled($row->id); 777 if ($gradingdisabled) { 778 return get_string('no'); 779 } else { 780 return get_string('yes'); 781 } 782 } 783 784 /** 785 * Format a column of data for display 786 * 787 * @param stdClass $row 788 * @return string 789 */ 790 public function col_grademax(stdClass $row) { 791 return format_float($this->assignment->get_instance()->grade, 2); 792 } 793 794 /** 795 * Format a column of data for display. 796 * 797 * @param stdClass $row 798 * @return string 799 */ 800 public function col_grade(stdClass $row) { 801 $o = ''; 802 803 $link = ''; 804 $separator = $this->output->spacer(array(), true); 805 $grade = ''; 806 $gradingdisabled = $this->assignment->grading_disabled($row->id); 807 808 if (!$this->is_downloading() && $this->hasgrade) { 809 $name = $this->assignment->fullname($row); 810 $icon = $this->output->pix_icon('gradefeedback', 811 get_string('gradeuser', 'assign', $name), 812 'mod_assign'); 813 $urlparams = array('id' => $this->assignment->get_course_module()->id, 814 'rownum'=>$this->rownum, 815 'action'=>'grade'); 816 $url = new moodle_url('/mod/assign/view.php', $urlparams); 817 $link = $this->output->action_link($url, $icon); 818 $grade .= $link . $separator; 819 } 820 821 $grade .= $this->display_grade($row->grade, 822 $this->quickgrading && !$gradingdisabled, 823 $row->userid, 824 $row->timemarked); 825 826 return $grade; 827 } 828 829 /** 830 * Format a column of data for display. 831 * 832 * @param stdClass $row 833 * @return string 834 */ 835 public function col_finalgrade(stdClass $row) { 836 $o = ''; 837 838 $grade = $this->get_gradebook_data_for_user($row->userid); 839 if ($grade) { 840 $o = $this->display_grade($grade->grade, false, $row->userid, $row->timemarked); 841 } 842 843 return $o; 844 } 845 846 /** 847 * Format a column of data for display. 848 * 849 * @param stdClass $row 850 * @return string 851 */ 852 public function col_timemarked(stdClass $row) { 853 $o = '-'; 854 855 if ($row->timemarked && $row->grade !== null && $row->grade >= 0) { 856 $o = userdate($row->timemarked); 857 } 858 if ($row->timemarked && $this->is_downloading()) { 859 // Force it for downloads as it affects import. 860 $o = userdate($row->timemarked); 861 } 862 863 return $o; 864 } 865 866 /** 867 * Format a column of data for display. 868 * 869 * @param stdClass $row 870 * @return string 871 */ 872 public function col_timesubmitted(stdClass $row) { 873 $o = '-'; 874 875 $group = false; 876 $submission = false; 877 $this->get_group_and_submission($row->id, $group, $submission, -1); 878 if ($group && $submission && $submission->timemodified) { 879 $o = userdate($submission->timemodified); 880 } else if ($row->timesubmitted) { 881 $o = userdate($row->timesubmitted); 882 } 883 884 return $o; 885 } 886 887 /** 888 * Format a column of data for display 889 * 890 * @param stdClass $row 891 * @return string 892 */ 893 public function col_status(stdClass $row) { 894 $o = ''; 895 896 $instance = $this->assignment->get_instance(); 897 898 $due = $instance->duedate; 899 if ($row->extensionduedate) { 900 $due = $row->extensionduedate; 901 } 902 903 $group = false; 904 $submission = false; 905 $this->get_group_and_submission($row->id, $group, $submission, -1); 906 if ($group && $submission) { 907 $timesubmitted = $submission->timemodified; 908 $status = $submission->status; 909 } else { 910 $timesubmitted = $row->timesubmitted; 911 $status = $row->status; 912 } 913 914 if ($this->assignment->is_any_submission_plugin_enabled()) { 915 916 $o .= $this->output->container(get_string('submissionstatus_' . $status, 'assign'), 917 array('class'=>'submissionstatus' .$status)); 918 if ($due && $timesubmitted > $due) { 919 $usertime = format_time($timesubmitted - $due); 920 $latemessage = get_string('submittedlateshort', 921 'assign', 922 $usertime); 923 $o .= $this->output->container($latemessage, 'latesubmission'); 924 } 925 if ($row->locked) { 926 $lockedstr = get_string('submissionslockedshort', 'assign'); 927 $o .= $this->output->container($lockedstr, 'lockedsubmission'); 928 } 929 930 // Add status of "grading", use markflow if enabled. 931 if ($instance->markingworkflow) { 932 $o .= $this->col_workflowstatus($row); 933 } else if ($row->grade !== null && $row->grade >= 0) { 934 $o .= $this->output->container(get_string('graded', 'assign'), 'submissiongraded'); 935 } else if (!$timesubmitted) { 936 $now = time(); 937 if ($due && ($now > $due)) { 938 $overduestr = get_string('overdue', 'assign', format_time($now - $due)); 939 $o .= $this->output->container($overduestr, 'overduesubmission'); 940 } 941 } 942 943 if ($row->extensionduedate) { 944 $userdate = userdate($row->extensionduedate); 945 $extensionstr = get_string('userextensiondate', 'assign', $userdate); 946 $o .= $this->output->container($extensionstr, 'extensiondate'); 947 } 948 } 949 950 if ($this->is_downloading()) { 951 $o = strip_tags(str_replace('</div>', "\n", $o)); 952 } 953 954 return $o; 955 } 956 957 /** 958 * Format a column of data for display. 959 * 960 * @param stdClass $row 961 * @return string 962 */ 963 public function col_userid(stdClass $row) { 964 global $USER; 965 966 $edit = ''; 967 968 $actions = array(); 969 970 $urlparams = array('id'=>$this->assignment->get_course_module()->id, 971 'rownum'=>$this->rownum, 972 'action'=>'grade'); 973 $url = new moodle_url('/mod/assign/view.php', $urlparams); 974 $noimage = null; 975 976 if (!$row->grade) { 977 $description = get_string('grade'); 978 } else { 979 $description = get_string('updategrade', 'assign'); 980 } 981 $actions['grade'] = new action_menu_link_secondary( 982 $url, 983 $noimage, 984 $description 985 ); 986 987 // Everything we need is in the row. 988 $submission = $row; 989 $flags = $row; 990 if ($this->assignment->get_instance()->teamsubmission) { 991 // Use the cache for this. 992 $submission = false; 993 $group = false; 994 $this->get_group_and_submission($row->id, $group, $submission, -1); 995 } 996 997 $submissionsopen = $this->assignment->submissions_open($row->id, 998 true, 999 $submission, 1000 $flags, 1001 $this->gradinginfo); 1002 $caneditsubmission = $this->assignment->can_edit_submission($row->id, $USER->id); 1003 1004 // Hide for offline assignments. 1005 if ($this->assignment->is_any_submission_plugin_enabled()) { 1006 if (!$row->status || 1007 $row->status == ASSIGN_SUBMISSION_STATUS_DRAFT || 1008 !$this->assignment->get_instance()->submissiondrafts) { 1009 1010 if (!$row->locked) { 1011 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1012 'userid'=>$row->id, 1013 'action'=>'lock', 1014 'sesskey'=>sesskey(), 1015 'page'=>$this->currpage); 1016 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1017 1018 $description = get_string('preventsubmissionsshort', 'assign'); 1019 $actions['lock'] = new action_menu_link_secondary( 1020 $url, 1021 $noimage, 1022 $description 1023 ); 1024 } else { 1025 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1026 'userid'=>$row->id, 1027 'action'=>'unlock', 1028 'sesskey'=>sesskey(), 1029 'page'=>$this->currpage); 1030 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1031 $description = get_string('allowsubmissionsshort', 'assign'); 1032 $actions['unlock'] = new action_menu_link_secondary( 1033 $url, 1034 $noimage, 1035 $description 1036 ); 1037 } 1038 } 1039 1040 if (($this->assignment->get_instance()->duedate || 1041 $this->assignment->get_instance()->cutoffdate) && 1042 $this->hasgrantextension) { 1043 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1044 'userid'=>$row->id, 1045 'action'=>'grantextension', 1046 'sesskey'=>sesskey(), 1047 'page'=>$this->currpage); 1048 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1049 $description = get_string('grantextension', 'assign'); 1050 $actions['grantextension'] = new action_menu_link_secondary( 1051 $url, 1052 $noimage, 1053 $description 1054 ); 1055 } 1056 if ($submissionsopen && 1057 $USER->id != $row->id && 1058 $caneditsubmission) { 1059 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1060 'userid'=>$row->id, 1061 'action'=>'editsubmission', 1062 'sesskey'=>sesskey(), 1063 'page'=>$this->currpage); 1064 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1065 $description = get_string('editsubmission', 'assign'); 1066 $actions['editsubmission'] = new action_menu_link_secondary( 1067 $url, 1068 $noimage, 1069 $description 1070 ); 1071 } 1072 } 1073 if ($row->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED && 1074 $this->assignment->get_instance()->submissiondrafts) { 1075 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1076 'userid'=>$row->id, 1077 'action'=>'reverttodraft', 1078 'sesskey'=>sesskey(), 1079 'page'=>$this->currpage); 1080 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1081 $description = get_string('reverttodraftshort', 'assign'); 1082 $actions['reverttodraft'] = new action_menu_link_secondary( 1083 $url, 1084 $noimage, 1085 $description 1086 ); 1087 } 1088 if ($row->status == ASSIGN_SUBMISSION_STATUS_DRAFT && 1089 $this->assignment->get_instance()->submissiondrafts && 1090 $caneditsubmission && 1091 $submissionsopen && 1092 $row->id != $USER->id) { 1093 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1094 'userid'=>$row->id, 1095 'action'=>'submitotherforgrading', 1096 'sesskey'=>sesskey(), 1097 'page'=>$this->currpage); 1098 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1099 $description = get_string('submitforgrading', 'assign'); 1100 $actions['submitforgrading'] = new action_menu_link_secondary( 1101 $url, 1102 $noimage, 1103 $description 1104 ); 1105 } 1106 1107 $ismanual = $this->assignment->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL; 1108 $hassubmission = !empty($row->status); 1109 $notreopened = $hassubmission && $row->status != ASSIGN_SUBMISSION_STATUS_REOPENED; 1110 $isunlimited = $this->assignment->get_instance()->maxattempts == ASSIGN_UNLIMITED_ATTEMPTS; 1111 $hasattempts = $isunlimited || $row->attemptnumber < $this->assignment->get_instance()->maxattempts - 1; 1112 1113 if ($ismanual && $hassubmission && $notreopened && $hasattempts) { 1114 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1115 'userid'=>$row->id, 1116 'action'=>'addattempt', 1117 'sesskey'=>sesskey(), 1118 'page'=>$this->currpage); 1119 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1120 $description = get_string('addattempt', 'assign'); 1121 $actions['addattempt'] = new action_menu_link_secondary( 1122 $url, 1123 $noimage, 1124 $description 1125 ); 1126 } 1127 1128 $menu = new action_menu(); 1129 $menu->set_owner_selector('.gradingtable-actionmenu'); 1130 $menu->set_alignment(action_menu::TL, action_menu::BL); 1131 $menu->set_constraint('.gradingtable > .no-overflow'); 1132 $menu->set_menu_trigger(get_string('edit')); 1133 foreach ($actions as $action) { 1134 $menu->add($action); 1135 } 1136 1137 // Prioritise the menu ahead of all other actions. 1138 $menu->prioritise = true; 1139 1140 $edit .= $this->output->render($menu); 1141 1142 return $edit; 1143 } 1144 1145 /** 1146 * Write the plugin summary with an optional link to view the full feedback/submission. 1147 * 1148 * @param assign_plugin $plugin Submission plugin or feedback plugin 1149 * @param stdClass $item Submission or grade 1150 * @param string $returnaction The return action to pass to the 1151 * view_submission page (the current page) 1152 * @param string $returnparams The return params to pass to the view_submission 1153 * page (the current page) 1154 * @return string The summary with an optional link 1155 */ 1156 private function format_plugin_summary_with_link(assign_plugin $plugin, 1157 stdClass $item, 1158 $returnaction, 1159 $returnparams) { 1160 $link = ''; 1161 $showviewlink = false; 1162 1163 $summary = $plugin->view_summary($item, $showviewlink); 1164 $separator = ''; 1165 if ($showviewlink) { 1166 $viewstr = get_string('view' . substr($plugin->get_subtype(), strlen('assign')), 'assign'); 1167 $icon = $this->output->pix_icon('t/preview', $viewstr); 1168 $urlparams = array('id' => $this->assignment->get_course_module()->id, 1169 'sid'=>$item->id, 1170 'gid'=>$item->id, 1171 'plugin'=>$plugin->get_type(), 1172 'action'=>'viewplugin' . $plugin->get_subtype(), 1173 'returnaction'=>$returnaction, 1174 'returnparams'=>http_build_query($returnparams)); 1175 $url = new moodle_url('/mod/assign/view.php', $urlparams); 1176 $link = $this->output->action_link($url, $icon); 1177 $separator = $this->output->spacer(array(), true); 1178 } 1179 1180 return $link . $separator . $summary; 1181 } 1182 1183 1184 /** 1185 * Format the submission and feedback columns. 1186 * 1187 * @param string $colname The column name 1188 * @param stdClass $row The submission row 1189 * @return mixed string or NULL 1190 */ 1191 public function other_cols($colname, $row) { 1192 // For extra user fields the result is already in $row. 1193 if (empty($this->plugincache[$colname])) { 1194 return $row->$colname; 1195 } 1196 1197 // This must be a plugin field. 1198 $plugincache = $this->plugincache[$colname]; 1199 1200 $plugin = $plugincache[0]; 1201 1202 $field = null; 1203 if (isset($plugincache[1])) { 1204 $field = $plugincache[1]; 1205 } 1206 1207 if ($plugin->is_visible() && $plugin->is_enabled()) { 1208 if ($plugin->get_subtype() == 'assignsubmission') { 1209 if ($this->assignment->get_instance()->teamsubmission) { 1210 $group = false; 1211 $submission = false; 1212 1213 $this->get_group_and_submission($row->id, $group, $submission, -1); 1214 if ($submission) { 1215 if ($submission->status == ASSIGN_SUBMISSION_STATUS_REOPENED) { 1216 // For a newly reopened submission - we want to show the previous submission in the table. 1217 $this->get_group_and_submission($row->id, $group, $submission, $submission->attemptnumber-1); 1218 } 1219 if (isset($field)) { 1220 return $plugin->get_editor_text($field, $submission->id); 1221 } 1222 return $this->format_plugin_summary_with_link($plugin, 1223 $submission, 1224 'grading', 1225 array()); 1226 } 1227 } else if ($row->submissionid) { 1228 if ($row->status == ASSIGN_SUBMISSION_STATUS_REOPENED) { 1229 // For a newly reopened submission - we want to show the previous submission in the table. 1230 $submission = $this->assignment->get_user_submission($row->userid, false, $row->attemptnumber - 1); 1231 } else { 1232 $submission = new stdClass(); 1233 $submission->id = $row->submissionid; 1234 $submission->timecreated = $row->firstsubmission; 1235 $submission->timemodified = $row->timesubmitted; 1236 $submission->assignment = $this->assignment->get_instance()->id; 1237 $submission->userid = $row->userid; 1238 $submission->attemptnumber = $row->attemptnumber; 1239 } 1240 // Field is used for only for import/export and refers the the fieldname for the text editor. 1241 if (isset($field)) { 1242 return $plugin->get_editor_text($field, $submission->id); 1243 } 1244 return $this->format_plugin_summary_with_link($plugin, 1245 $submission, 1246 'grading', 1247 array()); 1248 } 1249 } else { 1250 $grade = null; 1251 if (isset($field)) { 1252 return $plugin->get_editor_text($field, $row->gradeid); 1253 } 1254 1255 if ($row->gradeid) { 1256 $grade = new stdClass(); 1257 $grade->id = $row->gradeid; 1258 $grade->timecreated = $row->firstmarked; 1259 $grade->timemodified = $row->timemarked; 1260 $grade->assignment = $this->assignment->get_instance()->id; 1261 $grade->userid = $row->userid; 1262 $grade->grade = $row->grade; 1263 $grade->mailed = $row->mailed; 1264 $grade->attemptnumber = $row->attemptnumber; 1265 } 1266 if ($this->quickgrading && $plugin->supports_quickgrading()) { 1267 return $plugin->get_quickgrading_html($row->userid, $grade); 1268 } else if ($grade) { 1269 return $this->format_plugin_summary_with_link($plugin, 1270 $grade, 1271 'grading', 1272 array()); 1273 } 1274 } 1275 } 1276 return ''; 1277 } 1278 1279 /** 1280 * Using the current filtering and sorting - load all rows and return a single column from them. 1281 * 1282 * @param string $columnname The name of the raw column data 1283 * @return array of data 1284 */ 1285 public function get_column_data($columnname) { 1286 $this->setup(); 1287 $this->currpage = 0; 1288 $this->query_db($this->tablemaxrows); 1289 $result = array(); 1290 foreach ($this->rawdata as $row) { 1291 $result[] = $row->$columnname; 1292 } 1293 return $result; 1294 } 1295 1296 /** 1297 * Return things to the renderer. 1298 * 1299 * @return string the assignment name 1300 */ 1301 public function get_assignment_name() { 1302 return $this->assignment->get_instance()->name; 1303 } 1304 1305 /** 1306 * Return things to the renderer. 1307 * 1308 * @return int the course module id 1309 */ 1310 public function get_course_module_id() { 1311 return $this->assignment->get_course_module()->id; 1312 } 1313 1314 /** 1315 * Return things to the renderer. 1316 * 1317 * @return int the course id 1318 */ 1319 public function get_course_id() { 1320 return $this->assignment->get_course()->id; 1321 } 1322 1323 /** 1324 * Return things to the renderer. 1325 * 1326 * @return stdClass The course context 1327 */ 1328 public function get_course_context() { 1329 return $this->assignment->get_course_context(); 1330 } 1331 1332 /** 1333 * Return things to the renderer. 1334 * 1335 * @return bool Does this assignment accept submissions 1336 */ 1337 public function submissions_enabled() { 1338 return $this->assignment->is_any_submission_plugin_enabled(); 1339 } 1340 1341 /** 1342 * Return things to the renderer. 1343 * 1344 * @return bool Can this user view all grades (the gradebook) 1345 */ 1346 public function can_view_all_grades() { 1347 $context = $this->assignment->get_course_context(); 1348 return has_capability('gradereport/grader:view', $context) && 1349 has_capability('moodle/grade:viewall', $context); 1350 } 1351 1352 /** 1353 * Override the table show_hide_link to not show for select column. 1354 * 1355 * @param string $column the column name, index into various names. 1356 * @param int $index numerical index of the column. 1357 * @return string HTML fragment. 1358 */ 1359 protected function show_hide_link($column, $index) { 1360 if ($index > 0 || !$this->hasgrade) { 1361 return parent::show_hide_link($column, $index); 1362 } 1363 return ''; 1364 } 1365 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |