MDL-20636 Convert quiz statistics report.
[moodle.git] / mod / quiz / report / statistics / responseanalysis.php
CommitLineData
04853f27
TH
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * This file contains the code to analyse all the responses to a particular
20 * question.
21 *
22 * @package quiz_statistics
23 * @copyright 2010 The Open University
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
27
28/**
29 * This class can store and compute the analysis of the responses to a particular
30 * question.
31 *
32 * @copyright 2010 The Open University
33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34 */
35class quiz_statistics_response_analyser {
36 /** @var object the data from the database that defines the question. */
37 protected $questiondata;
38 protected $loaded = false;
39
40 /**
41 * @var array This is a multi-dimensional array that stores the results of
42 * the analysis.
43 *
44 * The description of {@link question_type::get_possible_responses()} should
45 * help understand this description.
46 *
47 * $this->responses[$subpartid][$responseclassid][$response] is an
48 * object with two fields, ->count and ->fraction.
49 */
50 public $responses = array();
51
52 /**
53 * @var array An array of
54 * $this->fractions[$subpartid][$responseclassid] is an object with two
55 * fields, ->responseclass and ->fraction.
56 */
57 public $responseclasses = array();
58
59 /**
60 * Create a new instance of this class for holding/computing the statistics
61 * for a particular question.
62 * @param object $questiondata the data from the database defining this question.
63 */
64 public function __construct($questiondata) {
65 $this->questiondata = $questiondata;
66
67 $this->responseclasses = question_bank::get_qtype($questiondata->qtype)->
68 get_possible_responses($questiondata);
69 foreach ($this->responseclasses as $subpartid => $responseclasses) {
70 foreach ($responseclasses as $responseclassid => $notused) {
71 $this->responses[$subpartid][$responseclassid] = array();
72 }
73 }
74 }
75
76 /**
77 * @return boolean whether this analysis has more than one subpart.
78 */
79 public function has_subparts() {
80 return count($this->responseclasses) > 1;
81 }
82
83 /**
84 * @return boolean whether this analysis has (a subpart with) more than one
85 * response class.
86 */
87 public function has_response_classes() {
88 foreach ($this->responseclasses as $partclasses) {
89 if (count($partclasses) > 1) {
90 return true;
91 }
92 }
93 return false;
94 }
95
96 /**
97 * @return boolean whether this analysis has a response class more than one
98 * different acutal response.
99 */
100 public function has_actual_responses() {
101 foreach ($this->responseclasses as $subpartid => $partclasses) {
102 foreach ($partclasses as $responseclassid => $notused) {
103 if (count($this->responses[$subpartid][$responseclassid]) > 1) {
104 return true;
105 }
106 }
107 }
108 return false;
109 }
110
111 /**
112 * Analyse all the response data for for all the specified attempts at
113 * this question.
114 * @param $qubaids which attempts to consider.
115 */
116 public function analyse($qubaids) {
117 // Load data.
118 $dm = new question_engine_data_mapper();
119 $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids);
120
121 // Analyse it.
122 foreach ($questionattempts as $qa) {
123 $this->add_data_from_one_attempt($qa);
124 }
125
126 $this->loaded = true;
127 }
128
129 /**
130 * Analyse the data from one question attempt.
131 * @param question_attempt $qa the data to analyse.
132 */
133 protected function add_data_from_one_attempt(question_attempt $qa) {
134 $blankresponse = question_classified_response::no_response();
135
136 $partresponses = $qa->classify_response();
137 foreach ($partresponses as $subpartid => $partresponse) {
138 if (!isset($this->responses[$subpartid][$partresponse->responseclassid][$partresponse->response])) {
139 $resp = new stdClass;
140 $resp->count = 0;
141 if (!is_null($partresponse->fraction)) {
142 $resp->fraction = $partresponse->fraction;
143 } else {
144 $resp->fraction = $this->responseclasses[$subpartid]
145 [$partresponse->responseclassid]->fraction;
146 }
147
148 $this->responses[$subpartid][$partresponse->responseclassid]
149 [$partresponse->response] = $resp;
150 }
151
152 $this->responses[$subpartid][$partresponse->responseclassid]
153 [$partresponse->response]->count += 1;
154 }
155 }
156
157 /**
158 * Store the computed response analysis in the quiz_question_response_stats
159 * table.
160 * @param integer $quizstatisticsid the cached quiz statistics to load the
161 * data corresponding to.
162 * @return boolean true if cached data was found in the database and loaded,
163 * otherwise false, to mean no data was loaded.
164 */
165 public function load_cached($quizstatisticsid) {
166 global $DB;
167
168 $rows = $DB->get_records('quiz_question_response_stats',
169 array('quizstatisticsid' => $quizstatisticsid, 'questionid' => $this->questiondata->id));
170 if (!$rows) {
171 return false;
172 }
173
174 foreach ($rows as $row) {
175 $this->responses[$row->subqid][$row->aid][$row->response]->count = $row->rcount;
176 $this->responses[$row->subqid][$row->aid][$row->response]->fraction = $row->credit;
177 }
178 $this->loaded = true;
179 return true;
180 }
181
182 /**
183 * Store the computed response analysis in the quiz_question_response_stats
184 * table.
185 * @param integer $quizstatisticsid the cached quiz statistics this correspons to.
186 */
187 public function store_cached($quizstatisticsid) {
188 global $DB;
189
190 if (!$this->loaded) {
191 throw new coding_exception('Question responses have not been analyised. Cannot store in the database.');
192 }
193
194 foreach ($this->responses as $subpartid => $partdata) {
195 foreach ($partdata as $responseclassid => $classdata) {
196 foreach ($classdata as $response => $data) {
197 $row = new stdClass;
198 $row->quizstatisticsid = $quizstatisticsid;
199 $row->questionid = $this->questiondata->id;
200 $row->subqid = $subpartid;
201 if ($responseclassid === '') {
202 $row->aid = null;
203 } else {
204 $row->aid = $responseclassid;
205 }
206 $row->response = addslashes($response);
207 $row->rcount = $data->count;
208 $row->credit = $data->fraction;
209 $DB->insert_record('quiz_question_response_stats', $row, false);
210 }
211 }
212 }
213 }
214}