See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 defines the quiz manual grading report class. 19 * 20 * @package quiz_grading 21 * @copyright 2006 Gustav Delius 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 require_once($CFG->dirroot . '/mod/quiz/report/grading/gradingsettings_form.php'); 29 30 31 /** 32 * Quiz report to help teachers manually grade questions that need it. 33 * 34 * This report basically provides two screens: 35 * - List question that might need manual grading (or optionally all questions). 36 * - Provide an efficient UI to grade all attempts at a particular question. 37 * 38 * @copyright 2006 Gustav Delius 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class quiz_grading_report extends quiz_default_report { 42 const DEFAULT_PAGE_SIZE = 5; 43 const DEFAULT_ORDER = 'random'; 44 45 /** @var array URL parameters for what is being displayed when grading. */ 46 protected $viewoptions = []; 47 48 /** @var int the current group, 0 if none, or NO_GROUPS_ALLOWED. */ 49 protected $currentgroup; 50 51 /** @var array from quiz_report_get_significant_questions. */ 52 protected $questions; 53 54 /** @var stdClass the course settings. */ 55 protected $course; 56 57 /** @var stdClass the course_module settings. */ 58 protected $cm; 59 60 /** @var stdClass the quiz settings. */ 61 protected $quiz; 62 63 /** @var context the quiz context. */ 64 protected $context; 65 66 /** @var quiz_grading_renderer Renderer of Quiz Grading. */ 67 protected $renderer; 68 69 /** @var string fragment of SQL code to restrict to the relevant users. */ 70 protected $userssql; 71 72 public function display($quiz, $cm, $course) { 73 74 $this->quiz = $quiz; 75 $this->cm = $cm; 76 $this->course = $course; 77 78 // Get the URL options. 79 $slot = optional_param('slot', null, PARAM_INT); 80 $questionid = optional_param('qid', null, PARAM_INT); 81 $grade = optional_param('grade', null, PARAM_ALPHA); 82 83 $includeauto = optional_param('includeauto', false, PARAM_BOOL); 84 if (!in_array($grade, array('all', 'needsgrading', 'autograded', 'manuallygraded'))) { 85 $grade = null; 86 } 87 $pagesize = optional_param('pagesize', 88 get_user_preferences('quiz_grading_pagesize', self::DEFAULT_PAGE_SIZE), 89 PARAM_INT); 90 $page = optional_param('page', 0, PARAM_INT); 91 $order = optional_param('order', 92 get_user_preferences('quiz_grading_order', self::DEFAULT_ORDER), 93 PARAM_ALPHA); 94 95 // Assemble the options required to reload this page. 96 $optparams = array('includeauto', 'page'); 97 foreach ($optparams as $param) { 98 if ($$param) { 99 $this->viewoptions[$param] = $$param; 100 } 101 } 102 if ($pagesize != self::DEFAULT_PAGE_SIZE) { 103 $this->viewoptions['pagesize'] = $pagesize; 104 } 105 if ($order != self::DEFAULT_ORDER) { 106 $this->viewoptions['order'] = $order; 107 } 108 109 // Check permissions. 110 $this->context = context_module::instance($this->cm->id); 111 require_capability('mod/quiz:grade', $this->context); 112 $shownames = has_capability('quiz/grading:viewstudentnames', $this->context); 113 $showidnumbers = has_capability('quiz/grading:viewidnumber', $this->context); 114 115 // Validate order. 116 if (!in_array($order, array('random', 'date', 'studentfirstname', 'studentlastname', 'idnumber'))) { 117 $order = self::DEFAULT_ORDER; 118 } else if (!$shownames && ($order == 'studentfirstname' || $order == 'studentlastname')) { 119 $order = self::DEFAULT_ORDER; 120 } else if (!$showidnumbers && $order == 'idnumber') { 121 $order = self::DEFAULT_ORDER; 122 } 123 if ($order == 'random') { 124 $page = 0; 125 } 126 127 // Get the list of questions in this quiz. 128 $this->questions = quiz_report_get_significant_questions($quiz); 129 if ($slot && !array_key_exists($slot, $this->questions)) { 130 throw new moodle_exception('unknownquestion', 'quiz_grading'); 131 } 132 133 // Process any submitted data. 134 if ($data = data_submitted() && confirm_sesskey() && $this->validate_submitted_marks()) { 135 $this->process_submitted_data(); 136 137 redirect($this->grade_question_url($slot, $questionid, $grade, $page + 1)); 138 } 139 140 // Get the group, and the list of significant users. 141 $this->currentgroup = $this->get_current_group($cm, $course, $this->context); 142 if ($this->currentgroup == self::NO_GROUPS_ALLOWED) { 143 $this->userssql = array(); 144 } else { 145 $this->userssql = get_enrolled_sql($this->context, 146 array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), $this->currentgroup); 147 } 148 149 $hasquestions = quiz_has_questions($this->quiz->id); 150 if (!$hasquestions) { 151 $this->print_header_and_tabs($cm, $course, $quiz, 'grading'); 152 echo $this->renderer->render_quiz_no_question_notification($quiz, $cm, $this->context); 153 return true; 154 } 155 156 if (!$slot) { 157 $this->display_index($includeauto); 158 return true; 159 } 160 161 // Display the grading UI for one question. 162 163 // Make sure there is something to do. 164 $counts = null; 165 $statecounts = $this->get_question_state_summary([$slot]); 166 foreach ($statecounts as $record) { 167 if ($record->questionid == $questionid) { 168 $counts = $record; 169 break; 170 } 171 } 172 173 // If not, redirect back to the list. 174 if (!$counts || $counts->$grade == 0) { 175 redirect($this->list_questions_url(), get_string('alldoneredirecting', 'quiz_grading')); 176 } 177 178 $this->display_grading_interface($slot, $questionid, $grade, 179 $pagesize, $page, $shownames, $showidnumbers, $order, $counts); 180 return true; 181 } 182 183 /** 184 * Get the JOIN conditions needed so we only show attempts by relevant users. 185 * 186 * @return qubaid_join 187 */ 188 protected function get_qubaids_condition() { 189 190 $where = "quiza.quiz = :mangrquizid AND 191 quiza.preview = 0 AND 192 quiza.state = :statefinished"; 193 $params = array('mangrquizid' => $this->cm->instance, 'statefinished' => quiz_attempt::FINISHED); 194 195 $usersjoin = ''; 196 $currentgroup = groups_get_activity_group($this->cm, true); 197 $enrolleduserscount = count_enrolled_users($this->context, 198 array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), $currentgroup); 199 if ($currentgroup) { 200 $userssql = get_enrolled_sql($this->context, 201 array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), $currentgroup); 202 if ($enrolleduserscount < 1) { 203 $where .= ' AND quiza.userid = 0'; 204 } else { 205 $usersjoin = "JOIN ({$userssql[0]}) AS enr ON quiza.userid = enr.id"; 206 $params += $userssql[1]; 207 } 208 } 209 210 return new qubaid_join("{quiz_attempts} quiza $usersjoin ", 'quiza.uniqueid', $where, $params); 211 } 212 213 /** 214 * Load the quiz_attempts rows corresponding to a list of question_usage ids. 215 * 216 * @param int[] $qubaids the question_usage ids of the quiz_attempts to load. 217 * @return array quiz attempts, with added user name fields. 218 */ 219 protected function load_attempts_by_usage_ids($qubaids) { 220 global $DB; 221 222 list($asql, $params) = $DB->get_in_or_equal($qubaids); 223 $params[] = quiz_attempt::FINISHED; 224 $params[] = $this->quiz->id; 225 226 $fields = 'quiza.*, u.idnumber, '; 227 $fields .= get_all_user_name_fields(true, 'u'); 228 $attemptsbyid = $DB->get_records_sql(" 229 SELECT $fields 230 FROM {quiz_attempts} quiza 231 JOIN {user} u ON u.id = quiza.userid 232 WHERE quiza.uniqueid $asql AND quiza.state = ? AND quiza.quiz = ?", 233 $params); 234 235 $attempts = array(); 236 foreach ($attemptsbyid as $attempt) { 237 $attempts[$attempt->uniqueid] = $attempt; 238 } 239 return $attempts; 240 } 241 242 /** 243 * Get the URL of the front page of the report that lists all the questions. 244 * 245 * @return moodle_url the URL. 246 */ 247 protected function base_url() { 248 return new moodle_url('/mod/quiz/report.php', 249 ['id' => $this->cm->id, 'mode' => 'grading']); 250 } 251 252 /** 253 * Get the URL of the front page of the report that lists all the questions. 254 * 255 * @param bool $includeauto if not given, use the current setting, otherwise, 256 * force a particular value of includeauto in the URL. 257 * @return moodle_url the URL. 258 */ 259 protected function list_questions_url($includeauto = null) { 260 $url = $this->base_url(); 261 262 $url->params($this->viewoptions); 263 264 if (!is_null($includeauto)) { 265 $url->param('includeauto', $includeauto); 266 } 267 268 return $url; 269 } 270 271 /** 272 * Get the URL to grade a batch of question attempts. 273 * 274 * @param int $slot 275 * @param int $questionid 276 * @param string $grade 277 * @param int|bool $page = true, link to current page. false = omit page. 278 * number = link to specific page. 279 * @return moodle_url 280 */ 281 protected function grade_question_url($slot, $questionid, $grade, $page = true) { 282 $url = $this->base_url(); 283 $url->params(['slot' => $slot, 'qid' => $questionid, 'grade' => $grade]); 284 $url->params($this->viewoptions); 285 286 if (!$page) { 287 $url->remove_params('page'); 288 } else if (is_integer($page)) { 289 $url->param('page', $page); 290 } 291 292 return $url; 293 } 294 295 /** 296 * Renders the contents of one cell of the table on the index view. 297 * 298 * @param stdClass $counts counts of different types of attempt for this slot. 299 * @param string $type the type of count to format. 300 * @param string $gradestring get_string identifier for the grading link text, if required. 301 * @return string HTML. 302 */ 303 protected function format_count_for_table($counts, $type, $gradestring) { 304 $result = $counts->$type; 305 if ($counts->$type > 0) { 306 $gradeurl = $this->grade_question_url($counts->slot, $counts->questionid, $type); 307 $result .= $this->renderer->render_grade_link($counts, $type, $gradestring, $gradeurl); 308 } 309 return $result; 310 } 311 312 /** 313 * Display the report front page which summarises the number of attempts to grade. 314 * 315 * @param bool $includeauto whether to show automatically-graded questions. 316 */ 317 protected function display_index($includeauto) { 318 global $PAGE; 319 320 $this->print_header_and_tabs($this->cm, $this->course, $this->quiz, 'grading'); 321 322 if ($groupmode = groups_get_activity_groupmode($this->cm)) { 323 // Groups is being used. 324 groups_print_activity_menu($this->cm, $this->list_questions_url()); 325 } 326 $statecounts = $this->get_question_state_summary(array_keys($this->questions)); 327 if ($includeauto) { 328 $linktext = get_string('hideautomaticallygraded', 'quiz_grading'); 329 } else { 330 $linktext = get_string('alsoshowautomaticallygraded', 'quiz_grading'); 331 } 332 echo $this->renderer->render_display_index_heading($linktext, $this->list_questions_url(!$includeauto)); 333 $data = []; 334 $header = []; 335 336 $header[] = get_string('qno', 'quiz_grading'); 337 $header[] = get_string('qtypeveryshort', 'question'); 338 $header[] = get_string('questionname', 'quiz_grading'); 339 $header[] = get_string('tograde', 'quiz_grading'); 340 $header[] = get_string('alreadygraded', 'quiz_grading'); 341 if ($includeauto) { 342 $header[] = get_string('automaticallygraded', 'quiz_grading'); 343 } 344 $header[] = get_string('total', 'quiz_grading'); 345 346 foreach ($statecounts as $counts) { 347 if ($counts->all == 0) { 348 continue; 349 } 350 if (!$includeauto && $counts->needsgrading == 0 && $counts->manuallygraded == 0) { 351 continue; 352 } 353 354 $row = []; 355 356 $row[] = $this->questions[$counts->slot]->number; 357 358 $row[] = $PAGE->get_renderer('question', 'bank')->qtype_icon($this->questions[$counts->slot]->type); 359 360 $row[] = format_string($counts->name); 361 362 $row[] = $this->format_count_for_table($counts, 'needsgrading', 'grade'); 363 364 $row[] = $this->format_count_for_table($counts, 'manuallygraded', 'updategrade'); 365 366 if ($includeauto) { 367 $row[] = $this->format_count_for_table($counts, 'autograded', 'updategrade'); 368 } 369 370 $row[] = $this->format_count_for_table($counts, 'all', 'gradeall'); 371 372 $data[] = $row; 373 } 374 echo $this->renderer->render_questions_table($includeauto, $data, $header); 375 } 376 377 /** 378 * Display the UI for grading attempts at one question. 379 * 380 * @param int $slot identifies which question to grade. 381 * @param int $questionid identifies which question to grade. 382 * @param string $grade type of attempts to grade. 383 * @param int $pagesize number of questions to show per page. 384 * @param int $page current page number. 385 * @param bool $shownames whether student names should be shown. 386 * @param bool $showidnumbers wither student idnumbers should be shown. 387 * @param string $order preferred order of attempts. 388 * @param stdClass $counts object that stores the number of each type of attempt. 389 */ 390 protected function display_grading_interface($slot, $questionid, $grade, 391 $pagesize, $page, $shownames, $showidnumbers, $order, $counts) { 392 393 if ($pagesize * $page >= $counts->$grade) { 394 $page = 0; 395 } 396 397 // Prepare the options form. 398 $hidden = [ 399 'id' => $this->cm->id, 400 'mode' => 'grading', 401 'slot' => $slot, 402 'qid' => $questionid, 403 'page' => $page, 404 ]; 405 if (array_key_exists('includeauto', $this->viewoptions)) { 406 $hidden['includeauto'] = $this->viewoptions['includeauto']; 407 } 408 $mform = new quiz_grading_settings_form($hidden, $counts, $shownames, $showidnumbers); 409 410 // Tell the form the current settings. 411 $settings = new stdClass(); 412 $settings->grade = $grade; 413 $settings->pagesize = $pagesize; 414 $settings->order = $order; 415 $mform->set_data($settings); 416 417 // If the form was submitted, save the user preferences, and 418 // redirect to a cleaned-up GET URL. 419 if ($mform->get_data()) { 420 set_user_preference('quiz_grading_pagesize', $pagesize); 421 set_user_preference('quiz_grading_order', $order); 422 redirect($this->grade_question_url($slot, $questionid, $grade, $page)); 423 } 424 425 list($qubaids, $count) = $this->get_usage_ids_where_question_in_state( 426 $grade, $slot, $questionid, $order, $page, $pagesize); 427 $attempts = $this->load_attempts_by_usage_ids($qubaids); 428 429 // Question info. 430 $questioninfo = new stdClass(); 431 $questioninfo->number = $this->questions[$slot]->number; 432 $questioninfo->questionname = format_string($counts->name); 433 434 // Paging info. 435 $paginginfo = new stdClass(); 436 $paginginfo->from = $page * $pagesize + 1; 437 $paginginfo->to = min(($page + 1) * $pagesize, $count); 438 $paginginfo->of = $count; 439 $qubaidlist = implode(',', $qubaids); 440 441 $this->print_header_and_tabs($this->cm, $this->course, $this->quiz, 'grading'); 442 443 $gradequestioncontent = ''; 444 foreach ($qubaids as $qubaid) { 445 $attempt = $attempts[$qubaid]; 446 $quba = question_engine::load_questions_usage_by_activity($qubaid); 447 $displayoptions = quiz_get_review_options($this->quiz, $attempt, $this->context); 448 $displayoptions->hide_all_feedback(); 449 $displayoptions->rightanswer = question_display_options::VISIBLE; 450 $displayoptions->history = question_display_options::HIDDEN; 451 $displayoptions->manualcomment = question_display_options::EDITABLE; 452 453 $gradequestioncontent .= $this->renderer->render_grade_question( 454 $quba, 455 $slot, 456 $displayoptions, 457 $this->questions[$slot]->number, 458 $this->get_question_heading($attempt, $shownames, $showidnumbers) 459 ); 460 } 461 462 $pagingbar = new stdClass(); 463 $pagingbar->count = $count; 464 $pagingbar->page = $page; 465 $pagingbar->pagesize = $pagesize; 466 $pagingbar->pagesize = $pagesize; 467 $pagingbar->order = $order; 468 $pagingbar->pagingurl = $this->grade_question_url($slot, $questionid, $grade, false); 469 470 $hiddeninputs = [ 471 'qubaids' => $qubaidlist, 472 'slots' => $slot, 473 'sesskey' => sesskey() 474 ]; 475 476 echo $this->renderer->render_grading_interface( 477 $questioninfo, 478 $this->list_questions_url(), 479 $mform, 480 $paginginfo, 481 $pagingbar, 482 $this->grade_question_url($slot, $questionid, $grade, $page), 483 $hiddeninputs, 484 $gradequestioncontent 485 ); 486 } 487 488 /** 489 * When saving a grading page, are all the submitted marks valid? 490 * 491 * @return bool true if all valid, else false. 492 */ 493 protected function validate_submitted_marks() { 494 495 $qubaids = optional_param('qubaids', null, PARAM_SEQUENCE); 496 if (!$qubaids) { 497 return false; 498 } 499 $qubaids = clean_param_array(explode(',', $qubaids), PARAM_INT); 500 501 $slots = optional_param('slots', '', PARAM_SEQUENCE); 502 if (!$slots) { 503 $slots = []; 504 } else { 505 $slots = explode(',', $slots); 506 } 507 508 foreach ($qubaids as $qubaid) { 509 foreach ($slots as $slot) { 510 if (!question_engine::is_manual_grade_in_range($qubaid, $slot)) { 511 return false; 512 } 513 } 514 } 515 516 return true; 517 } 518 519 /** 520 * Save all submitted marks to the database. 521 */ 522 protected function process_submitted_data() { 523 global $DB; 524 525 $qubaids = optional_param('qubaids', null, PARAM_SEQUENCE); 526 $assumedslotforevents = optional_param('slot', null, PARAM_INT); 527 528 if (!$qubaids) { 529 return; 530 } 531 532 $qubaids = clean_param_array(explode(',', $qubaids), PARAM_INT); 533 $attempts = $this->load_attempts_by_usage_ids($qubaids); 534 $events = []; 535 536 $transaction = $DB->start_delegated_transaction(); 537 foreach ($qubaids as $qubaid) { 538 $attempt = $attempts[$qubaid]; 539 $attemptobj = new quiz_attempt($attempt, $this->quiz, $this->cm, $this->course); 540 $attemptobj->process_submitted_actions(time()); 541 542 // Add the event we will trigger later. 543 $params = [ 544 'objectid' => $attemptobj->get_question_attempt($assumedslotforevents)->get_question_id(), 545 'courseid' => $attemptobj->get_courseid(), 546 'context' => context_module::instance($attemptobj->get_cmid()), 547 'other' => [ 548 'quizid' => $attemptobj->get_quizid(), 549 'attemptid' => $attemptobj->get_attemptid(), 550 'slot' => $assumedslotforevents, 551 ], 552 ]; 553 $events[] = \mod_quiz\event\question_manually_graded::create($params); 554 } 555 $transaction->allow_commit(); 556 557 // Trigger events for all the questions we manually marked. 558 foreach ($events as $event) { 559 $event->trigger(); 560 } 561 } 562 563 /** 564 * Load information about the number of attempts at various questions in each 565 * summarystate. 566 * 567 * The results are returned as an two dimensional array $qubaid => $slot => $dataobject 568 * 569 * @param array $slots A list of slots for the questions you want to konw about. 570 * @return array The array keys are slot,qestionid. The values are objects with 571 * fields $slot, $questionid, $inprogress, $name, $needsgrading, $autograded, 572 * $manuallygraded and $all. 573 */ 574 protected function get_question_state_summary($slots) { 575 $dm = new question_engine_data_mapper(); 576 return $dm->load_questions_usages_question_state_summary( 577 $this->get_qubaids_condition(), $slots); 578 } 579 580 /** 581 * Get a list of usage ids where the question with slot $slot, and optionally 582 * also with question id $questionid, is in summary state $summarystate. Also 583 * return the total count of such states. 584 * 585 * Only a subset of the ids can be returned by using $orderby, $limitfrom and 586 * $limitnum. A special value 'random' can be passed as $orderby, in which case 587 * $limitfrom is ignored. 588 * 589 * @param int $slot The slot for the questions you want to konw about. 590 * @param int $questionid (optional) Only return attempts that were of this specific question. 591 * @param string $summarystate 'all', 'needsgrading', 'autograded' or 'manuallygraded'. 592 * @param string $orderby 'random', 'date', 'student' or 'idnumber'. 593 * @param int $page implements paging of the results. 594 * Ignored if $orderby = random or $pagesize is null. 595 * @param int $pagesize implements paging of the results. null = all. 596 * @return array with two elements, an array of usage ids, and a count of the total number. 597 */ 598 protected function get_usage_ids_where_question_in_state($summarystate, $slot, 599 $questionid = null, $orderby = 'random', $page = 0, $pagesize = null) { 600 $dm = new question_engine_data_mapper(); 601 602 if ($pagesize && $orderby != 'random') { 603 $limitfrom = $page * $pagesize; 604 } else { 605 $limitfrom = 0; 606 } 607 608 $qubaids = $this->get_qubaids_condition(); 609 610 $params = []; 611 if ($orderby == 'date') { 612 list($statetest, $params) = $dm->in_summary_state_test( 613 'manuallygraded', false, 'mangrstate'); 614 $orderby = "( 615 SELECT MAX(sortqas.timecreated) 616 FROM {question_attempt_steps} sortqas 617 WHERE sortqas.questionattemptid = qa.id 618 AND sortqas.state $statetest 619 )"; 620 } else if ($orderby == 'studentfirstname' || $orderby == 'studentlastname' || $orderby == 'idnumber') { 621 $qubaids->from .= " JOIN {user} u ON quiza.userid = u.id "; 622 // For name sorting, map orderby form value to 623 // actual column names; 'idnumber' maps naturally. 624 switch ($orderby) { 625 case "studentlastname": 626 $orderby = "u.lastname, u.firstname"; 627 break; 628 case "studentfirstname": 629 $orderby = "u.firstname, u.lastname"; 630 break; 631 case "idnumber": 632 $orderby = "u.idnumber"; 633 break; 634 } 635 } 636 637 return $dm->load_questions_usages_where_question_in_state($qubaids, $summarystate, 638 $slot, $questionid, $orderby, $params, $limitfrom, $pagesize); 639 } 640 641 /** 642 * Initialise some parts of $PAGE and start output. 643 * 644 * @param object $cm the course_module information. 645 * @param object $course the course settings. 646 * @param object $quiz the quiz settings. 647 * @param string $reportmode the report name. 648 */ 649 public function print_header_and_tabs($cm, $course, $quiz, $reportmode = 'overview') { 650 global $PAGE; 651 $this->renderer = $PAGE->get_renderer('quiz_grading'); 652 parent::print_header_and_tabs($cm, $course, $quiz, $reportmode); 653 } 654 655 /** 656 * Get question heading. 657 * 658 * @param object $attempt an instance of quiz_attempt. 659 * @param bool $shownames True to show the question name. 660 * @param bool $showidnumbers True to show the question id number. 661 * @return string The string text for the question heading. 662 * @throws coding_exception 663 */ 664 protected function get_question_heading($attempt, $shownames, $showidnumbers) { 665 $a = new stdClass(); 666 $a->attempt = $attempt->attempt; 667 $a->fullname = fullname($attempt); 668 $a->idnumber = s($attempt->idnumber); 669 670 $showidnumbers = $showidnumbers && !empty($attempt->idnumber); 671 672 if ($shownames && $showidnumbers) { 673 return get_string('gradingattemptwithidnumber', 'quiz_grading', $a); 674 } else if ($shownames) { 675 return get_string('gradingattempt', 'quiz_grading', $a); 676 } else if ($showidnumbers) { 677 $a->fullname = $attempt->idnumber; 678 return get_string('gradingattempt', 'quiz_grading', $a); 679 } else { 680 return ''; 681 } 682 } 683 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body