2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * This file contains the code to analyse all the responses to a particular
22 * @subpackage questionbank
23 * @copyright 2013 Open University
24 * @author Jamie Pratt <me@jamiep.org>
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 namespace core_question\statistics\responses;
29 defined('MOODLE_INTERNAL') || die();
32 * This class can store and compute the analysis of the responses to a particular
35 * @copyright 2013 Open University
36 * @author Jamie Pratt <me@jamiep.org>
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 /** @var object full question data from db. */
41 protected $questiondata;
44 * @var analysis_for_question
49 * @var array Two index array first index is unique for each sub question part, the second index is the 'class' that this sub
50 * question part can be classified into. This is the return value from {@link \question_type::get_possible_responses()}
52 public $responseclasses = array();
55 * Create a new instance of this class for holding/computing the statistics
56 * for a particular question.
58 * @param object $questiondata the full question data from the database defining this question.
60 public function __construct($questiondata) {
61 $this->questiondata = $questiondata;
62 $qtypeobj = \question_bank::get_qtype($this->questiondata->qtype);
63 $this->analysis = new analysis_for_question($qtypeobj->get_possible_responses($this->questiondata));
68 * @return bool whether this analysis has more than one subpart.
70 public function has_subparts() {
71 return count($this->responseclasses) > 1;
75 * @return bool whether this analysis has (a subpart with) more than one response class.
77 public function has_response_classes() {
78 foreach ($this->responseclasses as $partclasses) {
79 if (count($partclasses) > 1) {
87 * @return bool whether this analysis has a response class more than one
88 * different acutal response, or if the actual response is different from
91 public function has_actual_responses() {
92 foreach ($this->responseclasses as $subpartid => $partclasses) {
93 foreach ($partclasses as $responseclassid => $modelresponse) {
94 $numresponses = count($this->responses[$subpartid][$responseclassid]);
95 if ($numresponses > 1) {
98 $actualresponse = key($this->responses[$subpartid][$responseclassid]);
99 if ($numresponses == 1 && $actualresponse != $modelresponse->responseclass) {
108 * Analyse all the response data for for all the specified attempts at
110 * @param \qubaid_condition $qubaids which attempts to consider.
111 * @return analysis_for_question
113 public function calculate($qubaids) {
115 $dm = new \question_engine_data_mapper();
116 $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids);
119 foreach ($questionattempts as $qa) {
120 $responseparts = $qa->classify_response();
121 $this->analysis->count_response_parts($responseparts);
123 $this->analysis->cache($qubaids, $this->questiondata->id);
124 return $this->analysis;
127 /** @var integer Time after which responses are automatically reanalysed. */
128 const TIME_TO_CACHE = 900; // 15 minutes.
132 * Retrieve the computed response analysis from the question_response_analysis table.
134 * @param \qubaid_condition $qubaids which attempts to get cached response analysis for.
135 * @return analysis_for_question|boolean analysis or false if no cached analysis found.
137 public function load_cached($qubaids) {
140 $timemodified = time() - self::TIME_TO_CACHE;
141 $rows = $DB->get_records_select('question_response_analysis', 'hashcode = ? AND questionid = ? AND timemodified > ? ',
142 array($qubaids->get_hash_code(), $this->questiondata->id, $timemodified));
147 foreach ($rows as $row) {
148 $class = $this->analysis->get_subpart($row->subqid)->get_response_class($row->aid);
149 $class->add_response_and_count($row->response, $row->credit, $row->rcount);
151 return $this->analysis;
156 * Find time of non-expired analysis in the database.
158 * @param $qubaids \qubaid_condition
159 * @return integer|boolean Time of cached record that matches this qubaid_condition or false if none found.
161 public function get_last_analysed_time($qubaids) {
164 $timemodified = time() - self::TIME_TO_CACHE;
165 return $DB->get_field_select('question_response_analysis', 'timemodified', 'hashcode = ? AND timemodified > ? '.
166 'ORDER BY timemodified DESC LIMIT 1',
167 array($qubaids->get_hash_code(), $timemodified));