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