Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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