Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file defines the quiz responses table for showing first or all tries at a question.
  19   *
  20   * @package   quiz_responses
  21   * @copyright 2014 The Open University
  22   * @author    Jamie Pratt <me@jamiep.org>
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  use mod_quiz\quiz_attempt;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  /**
  31   * This is a table subclass for displaying the quiz responses report, showing first or all tries.
  32   *
  33   * @package   quiz_responses
  34   * @copyright 2014 The Open University
  35   * @author    Jamie Pratt <me@jamiep.org>
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class quiz_first_or_all_responses_table extends quiz_last_responses_table {
  39  
  40      /**
  41       * The full question usage object for each try shown in report.
  42       *
  43       * @var question_usage_by_activity[]
  44       */
  45      protected $questionusagesbyactivity;
  46  
  47      protected function field_from_extra_data($tablerow, $slot, $field) {
  48          $questionattempt = $this->get_question_attempt($tablerow->usageid, $slot);
  49          switch($field) {
  50              case 'questionsummary' :
  51                  return $questionattempt->get_question_summary();
  52              case 'responsesummary' :
  53                  return $this->get_summary_after_try($tablerow, $slot);
  54              case 'rightanswer' :
  55                  return $questionattempt->get_right_answer_summary();
  56              default :
  57                  throw new coding_exception('Unknown question attempt field.');
  58          }
  59      }
  60  
  61  
  62      protected function load_extra_data() {
  63          if (count($this->rawdata) === 0) {
  64              return;
  65          }
  66          $qubaids = $this->get_qubaids_condition();
  67          $dm = new question_engine_data_mapper();
  68          $this->questionusagesbyactivity = $dm->load_questions_usages_by_activity($qubaids);
  69  
  70          // Insert an extra field in attempt data and extra rows where necessary.
  71          $newrawdata = [];
  72          foreach ($this->rawdata as $attempt) {
  73              if (!isset($this->questionusagesbyactivity[$attempt->usageid])) {
  74                  // This is a user without attempts.
  75                  $attempt->try = 0;
  76                  $attempt->lasttryforallparts = true;
  77                  $newrawdata[] = $attempt;
  78                  continue;
  79              }
  80  
  81              // We have an attempt, which may require several rows.
  82              $maxtriesinanyslot = 1;
  83              foreach ($this->questionusagesbyactivity[$attempt->usageid]->get_slots() as $slot) {
  84                  $tries = $this->get_no_of_tries($attempt, $slot);
  85                  $maxtriesinanyslot = max($maxtriesinanyslot, $tries);
  86              }
  87              for ($try = 1; $try <= $maxtriesinanyslot; $try++) {
  88                  $newtablerow = clone($attempt);
  89                  $newtablerow->lasttryforallparts = ($try == $maxtriesinanyslot);
  90                  if ($try !== $maxtriesinanyslot) {
  91                      $newtablerow->state = quiz_attempt::IN_PROGRESS;
  92                  }
  93                  $newtablerow->try = $try;
  94                  $newrawdata[] = $newtablerow;
  95                  if ($this->options->whichtries == question_attempt::FIRST_TRY) {
  96                      break;
  97                  }
  98              }
  99          }
 100          $this->rawdata = $newrawdata;
 101      }
 102  
 103      /**
 104       * Return the question attempt object.
 105       *
 106       * @param int $questionusagesid
 107       * @param int $slot
 108       * @return question_attempt
 109       */
 110      protected function get_question_attempt($questionusagesid, $slot) {
 111          return $this->questionusagesbyactivity[$questionusagesid]->get_question_attempt($slot);
 112      }
 113  
 114      /**
 115       * Find the state for $slot given after this try.
 116       *
 117       * @param stdClass $tablerow row data
 118       * @param int $slot Slot number.
 119       * @return question_state The question state after the attempt.
 120       */
 121      protected function slot_state($tablerow, $slot) {
 122          $qa = $this->get_question_attempt($tablerow->usageid, $slot);
 123          $submissionsteps = $qa->get_steps_with_submitted_response_iterator();
 124          $step = $submissionsteps[$tablerow->try];
 125          if ($step === null) {
 126              return null;
 127          }
 128          if ($this->is_last_try($tablerow, $slot, $tablerow->try)) {
 129              // If this is the last try then the step with the try data does not contain the correct state. We need to
 130              // use the last step's state, after the attempt has been finished.
 131              return $qa->get_state();
 132          }
 133          return $step->get_state();
 134      }
 135  
 136  
 137      /**
 138       * Get the summary of the response after the try.
 139       *
 140       * @param stdClass $tablerow row data
 141       * @param int $slot Slot number.
 142       * @return string summary for the question after this try.
 143       */
 144      public function get_summary_after_try($tablerow, $slot) {
 145          $qa = $this->get_question_attempt($tablerow->usageid, $slot);
 146          if (!($qa->get_question(false) instanceof question_manually_gradable)) {
 147              // No responses, and we cannot call summarise_response below.
 148              return null;
 149          }
 150          $submissionsteps = $qa->get_steps_with_submitted_response_iterator();
 151          $step = $submissionsteps[$tablerow->try];
 152          if ($step === null) {
 153              return null;
 154          }
 155          $qtdata = $step->get_qt_data();
 156          return $qa->get_question()->summarise_response($qtdata);
 157      }
 158  
 159      /**
 160       * Has this question usage been flagged?
 161       *
 162       * @param int $questionusageid Question usage id.
 163       * @param int $slot Slot number
 164       * @return bool Has it been flagged?
 165       */
 166      protected function is_flagged($questionusageid, $slot) {
 167          return $this->get_question_attempt($questionusageid, $slot)->is_flagged();
 168      }
 169  
 170      /**
 171       * The grade for this slot after this try.
 172       *
 173       * @param stdClass $tablerow attempt data from db.
 174       * @param int $slot Slot number.
 175       * @return float The fraction.
 176       */
 177      protected function slot_fraction($tablerow, $slot) {
 178          $qa = $this->get_question_attempt($tablerow->usageid, $slot);
 179          $submissionsteps = $qa->get_steps_with_submitted_response_iterator();
 180          $step = $submissionsteps[$tablerow->try];
 181          if ($step === null) {
 182              return null;
 183          }
 184          if ($this->is_last_try($tablerow, $slot, $tablerow->try)) {
 185              // If this is the last try then the step with the try data does not contain the correct fraction. We need to
 186              // use the last step's fraction, after the attempt has been finished.
 187              return $qa->get_fraction();
 188          }
 189          return $step->get_fraction();
 190      }
 191  
 192      /**
 193       * Is this the last try in the question attempt?
 194       *
 195       * @param stdClass $tablerow attempt data from db.
 196       * @param int $slot Slot number
 197       * @param int $tryno try no
 198       * @return bool Is it the last try?
 199       */
 200      protected function is_last_try($tablerow, $slot, $tryno) {
 201          return $tryno == $this->get_no_of_tries($tablerow, $slot);
 202      }
 203  
 204      /**
 205       * How many tries were attempted at this question in this slot, during this usage?
 206       *
 207       * @param stdClass $tablerow attempt data from db.
 208       * @param int $slot Slot number
 209       * @return int the number of tries in the question attempt for slot $slot.
 210       */
 211      public function get_no_of_tries($tablerow, $slot) {
 212          return count($this->get_question_attempt($tablerow->usageid, $slot)->get_steps_with_submitted_response_iterator());
 213      }
 214  
 215  
 216      /**
 217       * What is the step no this try was seen in?
 218       *
 219       * @param int $questionusageid The question usage id.
 220       * @param int $slot Slot number
 221       * @param int $tryno Try no
 222       * @return int the step no or zero if not found
 223       */
 224      protected function step_no_for_try($questionusageid, $slot, $tryno) {
 225          $qa = $this->get_question_attempt($questionusageid, $slot);
 226          return $qa->get_steps_with_submitted_response_iterator()->step_no_for_try($tryno);
 227      }
 228  
 229      public function col_checkbox($tablerow) {
 230          if ($tablerow->try != 1) {
 231              return '';
 232          } else {
 233              return parent::col_checkbox($tablerow);
 234          }
 235      }
 236  
 237      /**
 238       * Cell value function for email column. This extracts the contents for any cell in the email column from the row data.
 239       *
 240       * @param stdClass $tablerow Row data.
 241       * @return string   What to put in the cell for this column, for this row data.
 242       */
 243      public function col_email($tablerow) {
 244          if ($tablerow->try > 1) {
 245              return '';
 246          } else {
 247              return $tablerow->email;
 248          }
 249      }
 250  
 251      /**
 252       * Cell value function for sumgrades column. This extracts the contents for any cell in the sumgrades column from the row data.
 253       *
 254       * @param stdClass $tablerow Row data.
 255       * @return string   What to put in the cell for this column, for this row data.
 256       */
 257      public function col_sumgrades($tablerow) {
 258          if ($tablerow->try == 0) {
 259              // We are showing a user without a quiz attempt.
 260              return '-';
 261          } else if (!$tablerow->lasttryforallparts) {
 262              // There are more rows to come for this quiz attempt, so we will show this later.
 263              return '';
 264          } else {
 265              // Last row for this attempt. Now is the time to show attempt-related data.
 266              return parent::col_sumgrades($tablerow);
 267          }
 268      }
 269  
 270      public function col_state($tablerow) {
 271          if ($tablerow->try == 0) {
 272              // We are showing a user without a quiz attempt.
 273              return '-';
 274          } else if (!$tablerow->lasttryforallparts) {
 275              // There are more rows to come for this quiz attempt, so we will show this later.
 276              return '';
 277          } else {
 278              // Last row for this attempt. Now is the time to show attempt-related data.
 279              return parent::col_state($tablerow);
 280          }
 281      }
 282  
 283      public function get_row_class($tablerow) {
 284          if ($this->options->whichtries == question_attempt::ALL_TRIES && $tablerow->lasttryforallparts) {
 285              return 'lastrowforattempt';
 286          } else {
 287              return '';
 288          }
 289      }
 290  
 291      public function make_review_link($data, $tablerow, $slot) {
 292          if ($this->slot_state($tablerow, $slot) === null) {
 293              return $data;
 294          } else {
 295              return parent::make_review_link($data, $tablerow, $slot);
 296          }
 297      }
 298  }
 299  
 300