Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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