weekly release 2.6dev
[moodle.git] / mod / quiz / report / statistics / responseanalysis.php
CommitLineData
04853f27 1<?php
04853f27
TH
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 contains the code to analyse all the responses to a particular
19 * question.
20 *
8d76124c
TH
21 * @package quiz_statistics
22 * @copyright 2010 The Open University
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
04853f27
TH
24 */
25
26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
29
04853f27
TH
30/**
31 * This class can store and compute the analysis of the responses to a particular
32 * question.
33 *
8d76124c
TH
34 * @copyright 2010 The Open University
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
04853f27
TH
36 */
37class quiz_statistics_response_analyser {
38 /** @var object the data from the database that defines the question. */
39 protected $questiondata;
40 protected $loaded = false;
41
42 /**
43 * @var array This is a multi-dimensional array that stores the results of
44 * the analysis.
45 *
46 * The description of {@link question_type::get_possible_responses()} should
47 * help understand this description.
48 *
49 * $this->responses[$subpartid][$responseclassid][$response] is an
50 * object with two fields, ->count and ->fraction.
51 */
52 public $responses = array();
53
54 /**
0ff4bd08
TH
55 * @var array $this->fractions[$subpartid][$responseclassid] is an object
56 * with two fields, ->responseclass and ->fraction.
04853f27
TH
57 */
58 public $responseclasses = array();
59
60 /**
61 * Create a new instance of this class for holding/computing the statistics
62 * for a particular question.
63 * @param object $questiondata the data from the database defining this question.
64 */
65 public function __construct($questiondata) {
66 $this->questiondata = $questiondata;
67
55ca80ed
TH
68 $this->responseclasses =
69 question_bank::get_qtype($questiondata->qtype)->get_possible_responses(
70 $questiondata);
04853f27
TH
71 foreach ($this->responseclasses as $subpartid => $responseclasses) {
72 foreach ($responseclasses as $responseclassid => $notused) {
73 $this->responses[$subpartid][$responseclassid] = array();
74 }
75 }
76 }
77
78 /**
f7970e3c 79 * @return bool whether this analysis has more than one subpart.
04853f27
TH
80 */
81 public function has_subparts() {
82 return count($this->responseclasses) > 1;
83 }
84
85 /**
f7970e3c 86 * @return bool whether this analysis has (a subpart with) more than one
04853f27
TH
87 * response class.
88 */
89 public function has_response_classes() {
90 foreach ($this->responseclasses as $partclasses) {
91 if (count($partclasses) > 1) {
92 return true;
93 }
94 }
95 return false;
96 }
97
98 /**
f7970e3c 99 * @return bool whether this analysis has a response class more than one
e5b0920e
TH
100 * different acutal response, or if the actual response is different from
101 * the model response.
04853f27
TH
102 */
103 public function has_actual_responses() {
104 foreach ($this->responseclasses as $subpartid => $partclasses) {
e5b0920e
TH
105 foreach ($partclasses as $responseclassid => $modelresponse) {
106 $numresponses = count($this->responses[$subpartid][$responseclassid]);
107 if ($numresponses > 1) {
108 return true;
109 }
110 $actualresponse = key($this->responses[$subpartid][$responseclassid]);
111 if ($numresponses == 1 && $actualresponse != $modelresponse->responseclass) {
04853f27
TH
112 return true;
113 }
114 }
115 }
116 return false;
117 }
118
119 /**
120 * Analyse all the response data for for all the specified attempts at
121 * this question.
122 * @param $qubaids which attempts to consider.
123 */
124 public function analyse($qubaids) {
125 // Load data.
126 $dm = new question_engine_data_mapper();
127 $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids);
128
129 // Analyse it.
130 foreach ($questionattempts as $qa) {
131 $this->add_data_from_one_attempt($qa);
132 }
133
134 $this->loaded = true;
135 }
136
137 /**
138 * Analyse the data from one question attempt.
139 * @param question_attempt $qa the data to analyse.
140 */
141 protected function add_data_from_one_attempt(question_attempt $qa) {
142 $blankresponse = question_classified_response::no_response();
143
144 $partresponses = $qa->classify_response();
145 foreach ($partresponses as $subpartid => $partresponse) {
55ca80ed
TH
146 if (!isset($this->responses[$subpartid][$partresponse->responseclassid]
147 [$partresponse->response])) {
0ff4bd08 148 $resp = new stdClass();
04853f27
TH
149 $resp->count = 0;
150 if (!is_null($partresponse->fraction)) {
151 $resp->fraction = $partresponse->fraction;
152 } else {
153 $resp->fraction = $this->responseclasses[$subpartid]
154 [$partresponse->responseclassid]->fraction;
155 }
156
157 $this->responses[$subpartid][$partresponse->responseclassid]
158 [$partresponse->response] = $resp;
159 }
160
161 $this->responses[$subpartid][$partresponse->responseclassid]
162 [$partresponse->response]->count += 1;
163 }
164 }
165
166 /**
167 * Store the computed response analysis in the quiz_question_response_stats
168 * table.
f7970e3c 169 * @param int $quizstatisticsid the cached quiz statistics to load the
04853f27 170 * data corresponding to.
f7970e3c 171 * @return bool true if cached data was found in the database and loaded,
04853f27
TH
172 * otherwise false, to mean no data was loaded.
173 */
174 public function load_cached($quizstatisticsid) {
175 global $DB;
176
177 $rows = $DB->get_records('quiz_question_response_stats',
55ca80ed
TH
178 array('quizstatisticsid' => $quizstatisticsid,
179 'questionid' => $this->questiondata->id));
04853f27
TH
180 if (!$rows) {
181 return false;
182 }
183
184 foreach ($rows as $row) {
25123418 185 $this->responses[$row->subqid][$row->aid][$row->response] = new stdClass();
04853f27
TH
186 $this->responses[$row->subqid][$row->aid][$row->response]->count = $row->rcount;
187 $this->responses[$row->subqid][$row->aid][$row->response]->fraction = $row->credit;
188 }
189 $this->loaded = true;
190 return true;
191 }
192
193 /**
194 * Store the computed response analysis in the quiz_question_response_stats
195 * table.
f7970e3c 196 * @param int $quizstatisticsid the cached quiz statistics this correspons to.
04853f27
TH
197 */
198 public function store_cached($quizstatisticsid) {
199 global $DB;
200
201 if (!$this->loaded) {
55ca80ed
TH
202 throw new coding_exception(
203 'Question responses have not been analyised. Cannot store in the database.');
04853f27
TH
204 }
205
206 foreach ($this->responses as $subpartid => $partdata) {
207 foreach ($partdata as $responseclassid => $classdata) {
208 foreach ($classdata as $response => $data) {
0ff4bd08 209 $row = new stdClass();
04853f27
TH
210 $row->quizstatisticsid = $quizstatisticsid;
211 $row->questionid = $this->questiondata->id;
212 $row->subqid = $subpartid;
213 if ($responseclassid === '') {
214 $row->aid = null;
215 } else {
216 $row->aid = $responseclassid;
217 }
56b0df7e 218 $row->response = $response;
04853f27
TH
219 $row->rcount = $data->count;
220 $row->credit = $data->fraction;
221 $DB->insert_record('quiz_question_response_stats', $row, false);
222 }
223 }
224 }
225 }
226}