0c1c764e |
1 | <?php |
2 | /** |
fb94cd48 |
3 | * This script calculates various statistics about student attempts |
0c1c764e |
4 | * |
5 | * @version $Id$ |
fb94cd48 |
6 | * @author Martin Dougiamas, Jamie Pratt, Tim Hunt and others. |
0c1c764e |
7 | * @license http://www.gnu.org/copyleft/gpl.html GNU Public License |
8 | * @package quiz |
71a2b878 |
9 | **/ |
0c1c764e |
10 | |
71a2b878 |
11 | define('QUIZ_REPORT_TIME_TO_CACHE_STATS', MINSECS * 15); |
0c1c764e |
12 | require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_form.php'); |
13 | require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_table.php'); |
14 | |
c386eaa3 |
15 | class quiz_statistics_report extends quiz_default_report { |
43ec99aa |
16 | |
17 | /** |
18 | * @var object instance of table class used for main questions stats table. |
19 | */ |
20 | var $table; |
0c1c764e |
21 | |
22 | /** |
23 | * Display the report. |
24 | */ |
25 | function display($quiz, $cm, $course) { |
26 | global $CFG, $DB; |
27 | |
28 | $context = get_context_instance(CONTEXT_MODULE, $cm->id); |
29 | |
30 | $download = optional_param('download', '', PARAM_ALPHA); |
d1789d5d |
31 | $recalculate = optional_param('recalculate', 0, PARAM_BOOL); |
43ec99aa |
32 | //pass the question id for detailed analysis question |
33 | $qid = optional_param('qid', 0, PARAM_INT); |
0c1c764e |
34 | $pageoptions = array(); |
35 | $pageoptions['id'] = $cm->id; |
36 | $pageoptions['q'] = $quiz->id; |
37 | $pageoptions['mode'] = 'statistics'; |
71a2b878 |
38 | |
39 | $questions = quiz_report_load_questions($quiz); |
40 | // Load the question type specific information |
41 | if (!get_question_options($questions)) { |
42 | print_error('cannotloadquestion', 'question'); |
43 | } |
0c1c764e |
44 | |
71a2b878 |
45 | |
0c1c764e |
46 | $reporturl = new moodle_url($CFG->wwwroot.'/mod/quiz/report.php', $pageoptions); |
47 | |
48 | $mform = new mod_quiz_report_statistics($reporturl); |
49 | if ($fromform = $mform->get_data()){ |
50 | $useallattempts = $fromform->useallattempts; |
51 | if ($fromform->useallattempts){ |
52 | set_user_preference('quiz_report_statistics_useallattempts', $fromform->useallattempts); |
53 | } else { |
54 | unset_user_preference('quiz_report_statistics_useallattempts'); |
55 | } |
56 | } else { |
57 | $useallattempts = get_user_preferences('quiz_report_statistics_useallattempts', 0); |
58 | } |
59 | |
60 | /// find out current groups mode |
61 | $currentgroup = groups_get_activity_group($cm, true); |
43ec99aa |
62 | |
0c1c764e |
63 | |
e72efdd4 |
64 | $nostudentsingroup = false;//true if a group is selected and their is noeone in it. |
0c1c764e |
65 | if (!empty($currentgroup)) { |
66 | // all users who can attempt quizzes and who are in the currently selected group |
e72efdd4 |
67 | $groupstudents = get_users_by_capability($context, 'mod/quiz:attempt','','','','',$currentgroup,'',false); |
68 | if (!$groupstudents){ |
69 | $nostudentsingroup = true; |
0c1c764e |
70 | } |
71a2b878 |
71 | } else { |
72 | $groupstudents = array(); |
0c1c764e |
73 | } |
6f51ed72 |
74 | |
43ec99aa |
75 | if ($recalculate){ |
76 | if ($todelete = $DB->get_records_menu('quiz_statistics', array('quizid' => $quiz->id, 'groupid'=> (int)$currentgroup, 'allattempts'=>$useallattempts))){ |
77 | list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete)); |
78 | if (!$DB->delete_records_select('quiz_statistics', "id $todeletesql", $todeleteparams)){ |
79 | print_error('errordeletingquizstats', 'quiz_statistics'); |
80 | } |
81 | if (!$DB->delete_records_select('quiz_question_statistics', "quizstatisticsid $todeletesql", $todeleteparams)){ |
82 | print_error('errordeletingqstats', 'quiz_statistics'); |
83 | } |
84 | } |
85 | redirect($reporturl->out()); |
86 | } |
87 | |
88 | |
89 | $this->table = new quiz_report_statistics_table(); |
90 | $filename = "$course->shortname-".format_string($quiz->name,true); |
91 | $this->table->is_downloading($download, $filename, get_string('quizstructureanalysis', 'quiz_statistics')); |
92 | if (!$this->table->is_downloading()) { |
0c1c764e |
93 | // Only print headers if not asked to download data |
94 | $this->print_header_and_tabs($cm, $course, $quiz, "statistics"); |
95 | } |
96 | |
97 | if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used |
43ec99aa |
98 | if (!$this->table->is_downloading()) { |
0c1c764e |
99 | groups_print_activity_menu($cm, $reporturl->out()); |
43ec99aa |
100 | echo '<br />'; |
e72efdd4 |
101 | if ($currentgroup && !$groupstudents){ |
102 | notify(get_string('nostudentsingroup', 'quiz_statistics')); |
103 | } |
0c1c764e |
104 | } |
105 | } |
106 | |
43ec99aa |
107 | if (!$this->table->is_downloading()) { |
71a2b878 |
108 | // Print display options |
109 | $mform->set_data(array('useallattempts' => $useallattempts)); |
110 | $mform->display(); |
111 | } |
71a2b878 |
112 | |
43ec99aa |
113 | list($quizstats, $questions, $subquestions, $s, $usingattemptsstring) |
114 | = $this->quiz_questions_stats($quiz, $currentgroup, $nostudentsingroup, |
115 | $useallattempts, $groupstudents, $questions); |
116 | |
117 | if (!$this->table->is_downloading()){ |
118 | if ($s==0){ |
119 | print_heading(get_string('noattempts','quiz')); |
71a2b878 |
120 | } |
43ec99aa |
121 | } |
122 | if ($s){ |
123 | $this->table->setup($quiz, $cm->id, $reporturl, $s); |
124 | } |
125 | |
5153422c |
126 | if (!$qid){//main page |
43ec99aa |
127 | $this->output_quiz_stats_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl); |
128 | $this->output_question_stats_table($s, $questions, $subquestions); |
5153422c |
129 | $imageurl = $CFG->wwwroot.'/mod/quiz/report/statistics/statistics_graph.php?id='.$quizstats->id; |
130 | print_heading(get_string('statisticsreportgraph', 'quiz_statistics')); |
131 | echo '<div class="mdl-align"><img src="'.$imageurl.'" alt="'.get_string('statisticsreportgraph', 'quiz_statistics').'" /></div>'; |
132 | } else {//individual question page |
43ec99aa |
133 | $thisquestion = false; |
134 | if (isset($questions[$qid])){ |
135 | $thisquestion = $questions[$qid]; |
136 | } else if (isset($subquestions[$qid])){ |
137 | $thisquestion = $subquestions[$qid]; |
71a2b878 |
138 | } else { |
43ec99aa |
139 | print_error('questiondoesnotexist', 'question'); |
71a2b878 |
140 | } |
43ec99aa |
141 | $this->output_question_info_table($quiz, $thisquestion); |
142 | } |
143 | return true; |
144 | } |
145 | |
146 | function output_question_info_table($quiz, $question){ |
147 | $datumfromtable = $this->table->format_row($question); |
148 | |
149 | $questioninfotable = new object(); |
150 | $questioninfotable->align = array('center', 'center'); |
151 | $questioninfotable->width = '60%'; |
152 | $questioninfotable->class = 'generaltable titlesleft'; |
153 | |
154 | $questioninfotable->data = array(); |
155 | $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name); |
156 | $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'), $question->name.' '.$datumfromtable['actions']); |
157 | $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'), $question->qtype.' '.$datumfromtable['icon']); |
158 | $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'), $question->_stats->positions); |
159 | |
160 | $questionstatstable = new object(); |
161 | $questionstatstable->align = array('center', 'center'); |
162 | $questionstatstable->width = '60%'; |
163 | $questionstatstable->class = 'generaltable titlesleft'; |
164 | |
165 | unset($datumfromtable['number']); |
166 | unset($datumfromtable['icon']); |
167 | $actions = $datumfromtable['actions']; |
168 | unset($datumfromtable['actions']); |
169 | unset($datumfromtable['name']); |
170 | $labels = array('s' => get_string('attempts', 'quiz_statistics'), |
171 | 'facility' => get_string('facility', 'quiz_statistics'), |
172 | 'sd' => get_string('standarddeviationq', 'quiz_statistics'), |
173 | 'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'), |
174 | 'intended_weight'=> get_string('intended_weight', 'quiz_statistics'), |
175 | 'effective_weight'=> get_string('effective_weight', 'quiz_statistics'), |
176 | 'discrimination_index'=> get_string('discrimination_index', 'quiz_statistics'), |
177 | 'discriminative_efficiency'=> get_string('discriminative_efficiency', 'quiz_statistics')); |
178 | foreach ($datumfromtable as $item => $value){ |
179 | $questionstatstable->data[] = array($labels[$item], $value); |
180 | } |
181 | |
182 | print_heading(get_string('questioninformation', 'quiz_statistics')); |
183 | print_table($questioninfotable); |
184 | |
185 | print_box(format_text($question->questiontext).$actions, 'boxaligncenter generalbox boxwidthnormal mdl-align'); |
186 | |
187 | print_heading(get_string('questionstatistics', 'quiz_statistics')); |
188 | print_table($questionstatstable); |
189 | |
190 | print_heading(get_string('analysisofresponses', 'quiz_statistics')); |
191 | |
192 | } |
193 | |
194 | function output_question_stats_table($s, $questions, $subquestions){ |
195 | if (!$this->table->is_downloading()){ |
196 | print_heading(get_string('quizstructureanalysis', 'quiz_statistics')); |
197 | } |
198 | if ($s){ |
199 | foreach ($questions as $question){ |
200 | $this->table->add_data_keyed($this->table->format_row($question)); |
201 | if (!empty($question->_stats->subquestions)){ |
202 | $subitemstodisplay = explode(',', $question->_stats->subquestions); |
203 | foreach ($subitemstodisplay as $subitemid){ |
204 | $subquestions[$subitemid]->maxgrade = $question->maxgrade; |
205 | $this->table->add_data_keyed($this->table->format_row($subquestions[$subitemid])); |
206 | } |
71a2b878 |
207 | } |
71a2b878 |
208 | } |
43ec99aa |
209 | |
210 | $this->table->finish_output(); |
0c1c764e |
211 | } |
43ec99aa |
212 | } |
213 | |
214 | function output_quiz_stats_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl){ |
215 | global $DB; |
216 | // Print information on the number of existing attempts |
217 | $quizinformationtablehtml = print_heading(get_string('quizinformation', 'quiz_statistics'), '', 2, 'main', true); |
218 | $quizinformationtable = new object(); |
219 | $quizinformationtable->align = array('center', 'center'); |
220 | $quizinformationtable->width = '60%'; |
221 | $quizinformationtable->class = 'generaltable titlesleft'; |
222 | $quizinformationtable->data = array(); |
223 | $quizinformationtable->data[] = array(get_string('quizname', 'quiz_statistics'), $quiz->name); |
224 | $quizinformationtable->data[] = array(get_string('coursename', 'quiz_statistics'), $course->fullname); |
225 | if ($cm->idnumber){ |
226 | $quizinformationtable->data[] = array(get_string('coursename', 'quiz_statistics'), $cm->idnumber); |
227 | } |
228 | if ($quiz->timeopen){ |
229 | $quizinformationtable->data[] = array(get_string('quizopen', 'quiz'), userdate($quiz->timeopen)); |
230 | } |
231 | if ($quiz->timeclose){ |
232 | $quizinformationtable->data[] = array(get_string('quizclose', 'quiz'), userdate($quiz->timeclose)); |
233 | } |
234 | if ($quiz->timeopen && $quiz->timeclose){ |
235 | $quizinformationtable->data[] = array(get_string('duration', 'quiz_statistics'), format_time($quiz->timeclose - $quiz->timeopen)); |
236 | } |
237 | $format = array('firstattemptscount' => '', |
238 | 'allattemptscount' => '', |
239 | 'firstattemptsavg' => 'sumgrades_as_percentage', |
240 | 'allattemptsavg' => 'sumgrades_as_percentage', |
241 | 'median' => 'sumgrades_as_percentage', |
242 | 'standarddeviation' => 'sumgrades_as_percentage', |
243 | 'skewness' => '', |
244 | 'kurtosis' => '', |
245 | 'cic' => 'number_format', |
246 | 'errorratio' => 'number_format', |
247 | 'standarderror' => 'sumgrades_as_percentage'); |
248 | foreach ($quizstats as $property => $value){ |
249 | if (!isset($format[$property])){ |
250 | continue; |
71a2b878 |
251 | } |
43ec99aa |
252 | if (!is_null($value)){ |
71a2b878 |
253 | switch ($format[$property]){ |
254 | case 'sumgrades_as_percentage' : |
255 | $formattedvalue = quiz_report_scale_sumgrades_as_percentage($value, $quiz); |
256 | break; |
257 | case 'number_format' : |
f88fb62c |
258 | $formattedvalue = quiz_format_grade($quiz, $value).'%'; |
71a2b878 |
259 | break; |
260 | default : |
261 | $formattedvalue = $value; |
262 | } |
263 | $quizinformationtable->data[] = array(get_string($property, 'quiz_statistics', $usingattemptsstring), $formattedvalue); |
264 | } |
43ec99aa |
265 | } |
266 | if (!$this->table->is_downloading()){ |
d1789d5d |
267 | if (isset($quizstats->timemodified)){ |
268 | list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quiz->id, $currentgroup, $groupstudents, $useallattempts); |
269 | $sql = 'SELECT COUNT(1) ' . |
270 | 'FROM ' .$fromqa.' '. |
271 | 'WHERE ' .$whereqa.' AND qa.timefinish > :time'; |
272 | $a = new object(); |
273 | $a->lastcalculated = format_time(time() - $quizstats->timemodified); |
274 | if (!$a->count = $DB->count_records_sql($sql, array('time'=>$quizstats->timemodified)+$qaparams)){ |
275 | $a->count = 0; |
43ec99aa |
276 | } |
277 | $quizinformationtablehtml .= print_box_start('boxaligncenter generalbox boxwidthnormal mdl-align', '', true); |
278 | $quizinformationtablehtml .= get_string('lastcalculated', 'quiz_statistics', $a); |
279 | $quizinformationtablehtml .= print_single_button($reporturl->out(true), $reporturl->params()+array('recalculate'=>1), |
280 | get_string('recalculatenow', 'quiz_statistics'), 'post', '', true); |
281 | $quizinformationtablehtml .= print_box_end(true); |
d1789d5d |
282 | } |
e72efdd4 |
283 | } |
43ec99aa |
284 | $quizinformationtablehtml .= print_table($quizinformationtable, true); |
285 | if (!$this->table->is_downloading()){ |
286 | echo $quizinformationtablehtml; |
287 | } else { |
288 | $exportclass =& $this->table->export_class_instance(); |
289 | if ($download == 'xhtml'){ |
290 | echo $quizinformationtablehtml; |
291 | } else { |
292 | $exportclass->start_table(get_string('quizinformation', 'quiz_statistics')); |
293 | $headers = array(); |
294 | $row = array(); |
295 | foreach ($quizinformationtable->data as $data){ |
296 | $headers[]= $data[0]; |
297 | $row[] = $data[1]; |
71a2b878 |
298 | } |
43ec99aa |
299 | $exportclass->output_headers($headers); |
300 | $exportclass->add_data($row); |
301 | $exportclass->finish_table(); |
71a2b878 |
302 | } |
71a2b878 |
303 | } |
71a2b878 |
304 | } |
43ec99aa |
305 | |
71a2b878 |
306 | function quiz_stats($nostudentsingroup, $quizid, $currentgroup, $groupstudents, $questions, $useallattempts){ |
307 | global $CFG, $DB; |
e72efdd4 |
308 | if (!$nostudentsingroup){ |
309 | //Calculating_MEAN_of_grades_for_all_attempts_by_students |
310 | //http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Calculating_MEAN_of_grades_for_all_attempts_by_students |
71a2b878 |
311 | |
312 | list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents); |
313 | |
314 | $sql = 'SELECT (CASE WHEN attempt=1 THEN 1 ELSE 0 END) AS isfirst, COUNT(1) AS countrecs, SUM(sumgrades) AS total ' . |
315 | 'FROM '.$fromqa. |
316 | 'WHERE ' .$whereqa. |
317 | 'GROUP BY (attempt=1)'; |
e72efdd4 |
318 | if (!$attempttotals = $DB->get_records_sql($sql, $qaparams)){ |
e72efdd4 |
319 | $s = 0; |
320 | } else { |
321 | $firstattempt = $attempttotals[1]; |
322 | $allattempts = new object(); |
323 | $allattempts->countrecs = $firstattempt->countrecs + |
324 | (isset($attempttotals[0])?$attempttotals[0]->countrecs:0); |
325 | $allattempts->total = $firstattempt->total + |
326 | (isset($attempttotals[0])?$attempttotals[0]->total:0); |
327 | if ($useallattempts){ |
328 | $usingattempts = $allattempts; |
329 | $usingattempts->attempts = get_string('allattempts', 'quiz_statistics'); |
330 | $usingattempts->sql = ''; |
331 | } else { |
332 | $usingattempts = $firstattempt; |
333 | $usingattempts->attempts = get_string('firstattempts', 'quiz_statistics'); |
334 | $usingattempts->sql = 'AND qa.attempt=1 '; |
335 | } |
71a2b878 |
336 | $usingattemptsstring = $usingattempts->attempts; |
e72efdd4 |
337 | $s = $usingattempts->countrecs; |
ea751786 |
338 | $sumgradesavg = $usingattempts->total / $usingattempts->countrecs; |
e72efdd4 |
339 | } |
0c1c764e |
340 | } else { |
e72efdd4 |
341 | $s = 0; |
0c1c764e |
342 | } |
71a2b878 |
343 | $quizstats = new object(); |
344 | if ($s == 0){ |
345 | $quizstats->firstattemptscount = 0; |
346 | $quizstats->allattemptscount = 0; |
347 | } else { |
348 | $quizstats->firstattemptscount = $firstattempt->countrecs; |
349 | $quizstats->allattemptscount = $allattempts->countrecs; |
350 | $quizstats->firstattemptsavg = $firstattempt->total / $firstattempt->countrecs; |
351 | $quizstats->allattemptsavg = $allattempts->total / $allattempts->countrecs; |
0c1c764e |
352 | } |
71a2b878 |
353 | //recalculate sql again this time possibly including test for first attempt. |
354 | list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents, $useallattempts); |
355 | |
0c1c764e |
356 | //get the median |
71a2b878 |
357 | if ($s) { |
e72efdd4 |
358 | |
e72efdd4 |
359 | if (($s%2)==0){ |
0c1c764e |
360 | //even number of attempts |
e72efdd4 |
361 | $limitoffset = ($s/2) - 1; |
0c1c764e |
362 | $limit = 2; |
363 | } else { |
06d13248 |
364 | $limitoffset = (floor($s/2)); |
0c1c764e |
365 | $limit = 1; |
366 | } |
367 | $sql = 'SELECT id, sumgrades ' . |
08a7ead5 |
368 | 'FROM ' .$fromqa. |
369 | 'WHERE ' .$whereqa. |
0c1c764e |
370 | 'ORDER BY sumgrades'; |
e72efdd4 |
371 | if (!$mediangrades = $DB->get_records_sql_menu($sql, $qaparams, $limitoffset, $limit)){ |
0c1c764e |
372 | print_error('errormedian', 'quiz_statistics'); |
373 | } |
374 | if (count($mediangrades)==1){ |
71a2b878 |
375 | $quizstats->median = array_shift($mediangrades); |
0c1c764e |
376 | } else { |
377 | $median = array_shift($mediangrades); |
378 | $median += array_shift($mediangrades); |
71a2b878 |
379 | $quizstats->median = $median /2; |
0c1c764e |
380 | } |
6f51ed72 |
381 | if ($s>1){ |
6f51ed72 |
382 | //fetch sum of squared, cubed and power 4d |
383 | //differences between grades and mean grade |
e72efdd4 |
384 | $mean = $usingattempts->total / $s; |
6f51ed72 |
385 | $sql = "SELECT " . |
e72efdd4 |
386 | "SUM(POWER((qa.sumgrades - :mean1),2)) AS power2, " . |
387 | "SUM(POWER((qa.sumgrades - :mean2),3)) AS power3, ". |
388 | "SUM(POWER((qa.sumgrades - :mean3),4)) AS power4 ". |
6f51ed72 |
389 | 'FROM ' .$fromqa. |
71a2b878 |
390 | 'WHERE ' .$whereqa; |
e72efdd4 |
391 | $params = array('mean1' => $mean, 'mean2' => $mean, 'mean3' => $mean)+$qaparams; |
6f51ed72 |
392 | if (!$powers = $DB->get_record_sql($sql, $params)){ |
393 | print_error('errorpowers', 'quiz_statistics'); |
394 | } |
395 | |
396 | //Standard_Deviation |
397 | //see http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Standard_Deviation |
398 | |
71a2b878 |
399 | $quizstats->standarddeviation = sqrt($powers->power2 / ($s -1)); |
400 | |
6f51ed72 |
401 | |
402 | |
403 | //Skewness_and_Kurtosis |
404 | if ($s>2){ |
405 | //see http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Skewness_and_Kurtosis |
406 | $m2= $powers->power2 / $s; |
407 | $m3= $powers->power3 / $s; |
408 | $m4= $powers->power4 / $s; |
409 | |
410 | $k2= $s*$m2/($s-1); |
411 | $k3= $s*$s*$m3/(($s-1)*($s-2)); |
43ec99aa |
412 | if ($k2){ |
413 | $quizstats->skewness = $k3 / (pow($k2, 2/3)); |
414 | } |
6f51ed72 |
415 | } |
416 | |
417 | |
418 | if ($s>3){ |
419 | $k4= (($s*$s*$s)/(($s-1)*($s-2)*($s-3)))*((($s+1)*$m4)-(3*($s-1)*$m2*$m2)); |
43ec99aa |
420 | if ($k2){ |
421 | $quizstats->kurtosis = $k4 / ($k2*$k2); |
422 | } |
6f51ed72 |
423 | } |
e72efdd4 |
424 | } |
425 | } |
426 | if ($s){ |
4f5ffac0 |
427 | require_once("$CFG->dirroot/mod/quiz/report/statistics/qstats.php"); |
428 | $qstats = new qstats($questions, $s, $sumgradesavg); |
71a2b878 |
429 | $qstats->get_records($quizid, $currentgroup, $groupstudents, $useallattempts); |
4f5ffac0 |
430 | $qstats->process_states(); |
71a2b878 |
431 | } else { |
432 | $qstats = false; |
e72efdd4 |
433 | } |
71a2b878 |
434 | if ($s>1){ |
435 | $p = count($qstats->questions);//no of positions |
436 | if ($p > 1){ |
43ec99aa |
437 | if ($k2){ |
438 | $quizstats->cic = (100 * $p / ($p -1)) * (1 - ($qstats->sum_of_grade_variance())/$k2); |
439 | $quizstats->errorratio = 100 * sqrt(1-($quizstats->cic/100)); |
440 | $quizstats->standarderror = ($quizstats->errorratio * $quizstats->standarddeviation / 100); |
441 | } |
4f5ffac0 |
442 | } |
08a7ead5 |
443 | } |
71a2b878 |
444 | return array($s, $usingattemptsstring, $quizstats, $qstats); |
0c1c764e |
445 | } |
43ec99aa |
446 | |
447 | function quiz_questions_stats($quiz, $currentgroup, $nostudentsingroup, $useallattempts, $groupstudents, $questions){ |
448 | global $DB; |
449 | $timemodified = time() - QUIZ_REPORT_TIME_TO_CACHE_STATS; |
450 | $params = array('quizid'=>$quiz->id, 'groupid'=>(int)$currentgroup, 'allattempts'=>$useallattempts, 'timemodified'=>$timemodified); |
451 | if (!$quizstats = $DB->get_record_select('quiz_statistics', 'quizid = :quizid AND groupid = :groupid AND allattempts = :allattempts AND timemodified > :timemodified', $params, '*', true)){ |
452 | list($s, $usingattemptsstring, $quizstats, $qstats) = $this->quiz_stats($nostudentsingroup, $quiz->id, $currentgroup, $groupstudents, $questions, $useallattempts); |
453 | $toinsert = (object)((array)$quizstats + $params); |
454 | $toinsert->timemodified = time(); |
5153422c |
455 | $quizstats->id = $DB->insert_record('quiz_statistics', $toinsert); |
43ec99aa |
456 | foreach ($qstats->questions as $question){ |
5153422c |
457 | $question->_stats->quizstatisticsid = $quizstats->id; |
43ec99aa |
458 | $DB->insert_record('quiz_question_statistics', $question->_stats, false, true); |
459 | } |
460 | foreach ($qstats->subquestions as $subquestion){ |
5153422c |
461 | $subquestion->_stats->quizstatisticsid = $quizstats->id; |
43ec99aa |
462 | $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true); |
463 | } |
464 | if (isset($qstats)){ |
465 | $questions = $qstats->questions; |
466 | $subquestions = $qstats->subquestions; |
467 | } else { |
468 | $questions = array(); |
469 | $subquestions = array(); |
470 | } |
471 | } else { |
472 | //use cached results |
473 | if ($useallattempts){ |
474 | $usingattemptsstring = get_string('allattempts', 'quiz_statistics'); |
475 | $s = $quizstats->allattemptscount; |
476 | } else { |
477 | $usingattemptsstring = get_string('firstattempts', 'quiz_statistics'); |
478 | $s = $quizstats->firstattemptscount; |
479 | } |
480 | $questionstats = $DB->get_records('quiz_question_statistics', array('quizstatisticsid'=>$quizstats->id), 'subquestion ASC'); |
481 | $questionstats = quiz_report_index_by_keys($questionstats, array('subquestion', 'questionid')); |
482 | if (1 < count($questionstats)){ |
483 | list($mainquestionstats, $subquestionstats) = $questionstats; |
484 | $subqstofetch = array_keys($subquestionstats); |
485 | $subquestions = question_load_questions($subqstofetch); |
486 | foreach (array_keys($subquestions) as $subqid){ |
487 | $subquestions[$subqid]->_stats = $subquestionstats[$subqid]; |
488 | } |
489 | } else { |
490 | $mainquestionstats = $questionstats[0]; |
491 | $subquestions = array(); |
492 | } |
493 | foreach (array_keys($questions) as $qid){ |
494 | $questions[$qid]->_stats = $mainquestionstats[$qid]; |
495 | } |
496 | } |
497 | return array($quizstats, $questions, $subquestions, $s, $usingattemptsstring); |
498 | } |
0c1c764e |
499 | } |
71a2b878 |
500 | function quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents, $allattempts = true){ |
06d13248 |
501 | global $DB; |
71a2b878 |
502 | $fromqa = '{quiz_attempts} qa '; |
d1789d5d |
503 | $whereqa = 'qa.quiz = :quizid AND qa.preview=0 AND qa.timefinish !=0 '; |
71a2b878 |
504 | $qaparams = array('quizid'=>$quizid); |
505 | if (!empty($currentgroup) && $groupstudents) { |
506 | list($grpsql, $grpparams) = $DB->get_in_or_equal(array_keys($groupstudents), SQL_PARAMS_NAMED, 'u0000'); |
507 | $whereqa .= 'AND qa.userid '.$grpsql.' '; |
508 | $qaparams += $grpparams; |
509 | } |
510 | if (!$allattempts){ |
511 | $whereqa .= 'AND qa.attempt=1 '; |
512 | } |
513 | return array($fromqa, $whereqa, $qaparams); |
514 | } |
43ec99aa |
515 | function quiz_report_safe_divider($dividend, $divisor){ |
516 | if ($divisor == 0){ |
517 | return null; |
518 | } |
519 | return $dividend / $divisor; |
520 | } |
0c1c764e |
521 | ?> |