MDL-13678 "Change default number of rows per page on quiz reports" Made a new constan...
[moodle.git] / mod / quiz / report / analysis / report.php
1 <?php  // $Id$
3     require_once($CFG->libdir.'/tablelib.php');
5 /// Item analysis displays a table of quiz questions and their performance
6 class quiz_report extends quiz_default_report {
8     function display($quiz, $cm, $course) {     /// This function just displays the report
9         global $CFG, $SESSION, $QTYPES;
10         $strnoattempts = get_string('noattempts','quiz');
11     /// Only print headers if not asked to download data
12         $download = optional_param('download', NULL);
13         if (!$download) {
14             $this->print_header_and_tabs($cm, $course, $quiz, $reportmode="analysis");
15         }
16     /// Construct the table for this particular report
17 //    echo "course <pre>";print_r($course);echo "</pre>";
18  //   echo "course <pre>";print_r($CFG);echo "</pre>";
19         if (!$quiz->questions) {
20             print_heading($strnoattempts);
21             return true;
22         }
24     /// Check to see if groups are being used in this quiz
25         $currentgroup = groups_get_activity_group($cm, true);
26         
27         if ($groupmode = groups_get_activity_groupmode($cm)) {   // Groups are being used
28             if (!$download) {
29                 groups_print_activity_menu($cm, "report.php?id=$cm->id&amp;mode=analysis");
30             }
31         }
33         // set Table and Analysis stats options
34         if(!isset($SESSION->quiz_analysis_table)) {
35             $SESSION->quiz_analysis_table = array('attemptselection' => 0, 'lowmarklimit' => 0, 'pagesize' => QUIZ_REPORT_DEFAULT_PAGE_SIZE);
36         }
38         foreach($SESSION->quiz_analysis_table as $option => $value) {
39             $urlparam = optional_param($option, NULL, PARAM_INT);
40             if($urlparam === NULL) {
41                 $$option = $value;
42             } else {
43                 $$option = $SESSION->quiz_analysis_table[$option] = $urlparam;
44             }
45         }
46         if (!isset($pagesize) || ((int)$pagesize < 1) ){
47             $pagesize = QUIZ_REPORT_DEFAULT_PAGE_SIZE;
48         }
51         $scorelimit = $quiz->sumgrades * $lowmarklimit/ 100;
53         // ULPGC ecastro DEBUG this is here to allow for different SQL to select attempts
54         switch ($attemptselection) {
55         case QUIZ_ALLATTEMPTS :
56             $limit = '';
57             $group = '';
58             break;
59         case QUIZ_HIGHESTATTEMPT :
60             $limit = ', max(qa.sumgrades) ';
61             $group = ' GROUP BY qa.userid ';
62             break;
63         case QUIZ_FIRSTATTEMPT :
64             $limit = ', min(qa.timemodified) ';
65             $group = ' GROUP BY qa.userid ';
66             break;
67         case QUIZ_LASTATTEMPT :
68             $limit = ', max(qa.timemodified) ';
69             $group = ' GROUP BY qa.userid ';
70             break;
71         }
73         if ($attemptselection != QUIZ_ALLATTEMPTS) {
74             $sql = 'SELECT qa.userid '.$limit.
75                     'FROM '.$CFG->prefix.'user u LEFT JOIN '.$CFG->prefix.'quiz_attempts qa ON u.id = qa.userid '.
76                     'WHERE qa.quiz = '.$quiz->id.' AND qa.preview = 0 '.
77                     $group;
78             $usermax = get_records_sql_menu($sql);
79         }else {
80             $usermax = '';
81         }
82         $groupmembers = '';
83         $groupwhere = '';
85         //Add this to the SQL to show only group users
86         if ($currentgroup) {
87             $groupmembers = ", {$CFG->prefix}groups_members gm ";
88             $groupwhere = "AND gm.groupid = '$currentgroup' AND u.id = gm.userid";
89         }
91         $sql = 'SELECT  qa.* FROM '.$CFG->prefix.'quiz_attempts qa, '.$CFG->prefix.'user u '.$groupmembers.
92                  'WHERE u.id = qa.userid AND qa.quiz = '.$quiz->id.' AND qa.preview = 0 AND ( qa.sumgrades >= '.$scorelimit.' ) '.$groupwhere;
94         // ^^^^^^ es posible seleccionar aqu TODOS los quizzes, como quiere Jussi,
95         // pero habra que llevar la cuenta ed cada quiz para restaura las preguntas (quizquestions, states)
97         /// Fetch the attempts
98         $attempts = get_records_sql($sql);
100         if(empty($attempts)) {
101             print_heading(get_string('nothingtodisplay'));
102             $this->print_options_form($quiz, $cm, $attemptselection, $lowmarklimit, $pagesize);
103             return true;
104         }
105         $questions = array();
106         $getquestiondata = true ;
107         $this->get_questions_atttempts_data ($quiz,&$questions,&$attempts,$attemptselection,$usermax);
109 /*    This is the code before creating the get_questions_atttempts_data function
110 ///     will remove these lines when the new function gets approval 
111 /// Here we rewiew all attempts and record data to construct the table
112         $questions = array();
113         $statstable = array();
114         $questionarray = array();
115         foreach ($attempts as $attempt) {
116             $questionarray[] = quiz_questions_in_quiz($attempt->layout);
117         }
118         $questionlist = quiz_questions_in_quiz(implode(",", $questionarray));
119         $questionarray = array_unique(explode(",",$questionlist));
120         $questionlist = implode(",", $questionarray);
121         unset($questionarray);
123         foreach ($attempts as $attempt) {
124             switch ($attemptselection) {
125             case QUIZ_ALLATTEMPTS :
126                 $userscore = 0;      // can be anything, not used
127                 break;
128             case QUIZ_HIGHESTATTEMPT :
129                 $userscore = $attempt->sumgrades;
130                 break;
131             case QUIZ_FIRSTATTEMPT :
132                 $userscore = $attempt->timemodified;
133                 break;
134             case QUIZ_LASTATTEMPT :
135                 $userscore = $attempt->timemodified;
136                 break;
137             }
139             if ($attemptselection == QUIZ_ALLATTEMPTS || $userscore == $usermax[$attempt->userid]) {
141             $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
142                    "  FROM {$CFG->prefix}question q,".
143                    "       {$CFG->prefix}quiz_question_instances i".
144                    " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
145                    "   AND q.id IN ($questionlist)";
147             if (!$quizquestions = get_records_sql($sql)) {
148                 print_error('No questions found');
149             }
151             // Load the question type specific information
152             if (!get_question_options($quizquestions)) {
153                 print_error('Could not load question options');
154             }
155             // Restore the question sessions to their most recent states
156             // creating new sessions where required
157             if (!$states = get_question_states($quizquestions, $quiz, $attempt)) {
158                 print_error('Could not restore question sessions');
159             }
160             $numbers = explode(',', $questionlist);
161             $statsrow = array();
162             foreach ($numbers as $i) {
163                 if (!isset($quizquestions[$i]) or !isset($states[$i])) {
164                     continue;
165                 }
166                 $qtype = ($quizquestions[$i]->qtype=='random') ? $states[$i]->options->question->qtype : $quizquestions[$i]->qtype;
167                 if($quizquestions[$i]->qtype =='randomsamatch'){
168                     $quizquestions[$i]->options =$states[$i]->options ;
169                 }   
170                 $q = get_question_responses($quizquestions[$i], $states[$i]);
171                 if (empty($q)){
172                     continue;
173                 }
174                 $qid = $q->id;
175                 if (!isset($questions[$qid])) {
176                     $questions[$qid]['id'] = $qid;
177                     $questions[$qid]['qname'] = $quizquestions[$i]->name;
178                     foreach ($q->responses as $answer => $r) {
179                         $r->count = 0;
180                         $questions[$qid]['responses'][$answer] = $r->answer;
181                         $questions[$qid]['rcounts'][$answer] = 0;
182                         $questions[$qid]['credits'][$answer] = $r->credit;
183                         $statsrow[$qid] = 0;
184                     }
185                 }
186                 $responses = get_question_actual_response($quizquestions[$i], $states[$i]);
187                 foreach ($responses as $resp){
188                     if ($resp) {
189                         if ($key = array_search($resp, $questions[$qid]['responses'])) {
190                             $questions[$qid]['rcounts'][$key]++;
191                         } else {
192                             $test = new stdClass;
193                             $test->responses = $QTYPES[$quizquestions[$i]->qtype]->get_correct_responses($quizquestions[$i], $states[$i]);
194                             if ($key = $QTYPES[$quizquestions[$i]->qtype]->check_response($quizquestions[$i], $states[$i], $test)) {
195                                 $questions[$qid]['rcounts'][$key]++;
196                             } else {
197                                 $questions[$qid]['responses'][] = $resp;
198                                 $questions[$qid]['rcounts'][] = 1;
199                                 $questions[$qid]['credits'][] = 0;
200                             }
201                         }
202                     }
203                 }
204                 $statsrow[$qid] = get_question_fraction_grade($quizquestions[$i], $states[$i]);
205             }
206             $attemptscores[$attempt->id] = $attempt->sumgrades;
207             $statstable[$attempt->id] = $statsrow;
208             }
209         } // Statistics Data table built
211         unset($attempts);
212         unset($quizquestions);
213         unset($states);
215         // now calculate statistics and set the values in the $questions array
216         $top = max($attemptscores);
217         $bottom = min($attemptscores);
218         $gap = ($top - $bottom)/3;
219         $top -=$gap;
220         $bottom +=$gap;
221         foreach ($questions as $qid=>$q) {
222             $questions[$qid] = $this->report_question_stats($q, $attemptscores, $statstable, $top, $bottom);
223             // calculate rpercent
224             foreach($q['responses']as $aid => $resp){
225                 $rpercent =  '('.format_float($q['rcounts'][$aid]/$q['count']*100,0).'%)';
226                 $questions[$qid]['rpercent'][$aid] = $rpercent ;
227             }
228   
229         }
230                 
231             
232         unset($attemptscores);
233         unset($statstable);
234 */
235     /// Now check if asked download of data
236         if ($download = optional_param('download', NULL)) {
237             $filename = clean_filename("$course->shortname ".format_string($quiz->name,true));
238             switch ($download) {
239             case "Excel" :
240                 $this->Export_Excel($questions, $filename);
241                 break;
242             case "ODS":
243                 $this->Export_ODS($questions, $filename);
244                 break;
245             case "CSV":
246                 $this->Export_CSV($questions, $filename);
247                 break;            
248             case "HTML":
249                 $this->Export_HTML($questions, $filename);
250                 break;
251             }
252         }
254     /// Construct the table for this particular report
256         $tablecolumns = array('id', 'qname',    'responses', 'credits', 'rcounts', 'rpercent', 'facility', 'qsd','disc_index', 'disc_coeff');
257         $tableheaders = array(get_string('qidtitle','quiz_analysis'), get_string('qtexttitle','quiz_analysis'),
258                         get_string('responsestitle','quiz_analysis'), get_string('rfractiontitle','quiz_analysis'),
259                         get_string('rcounttitle','quiz_analysis'), get_string('rpercenttitle','quiz_analysis'),
260                         get_string('facilitytitle','quiz_analysis'), get_string('stddevtitle','quiz_analysis'),
261                         get_string('dicsindextitle','quiz_analysis'), get_string('disccoefftitle','quiz_analysis'));
263         $table = new flexible_table('mod-quiz-report-itemanalysis');
265         $table->define_columns($tablecolumns);
266         $table->define_headers($tableheaders);
267         $table->define_baseurl($CFG->wwwroot.'/mod/quiz/report.php?q='.$quiz->id.'&amp;mode=analysis');
269         $table->sortable(true);
270       //  $table->no_sorting('rpercent');
271         $table->collapsible(true);
272         $table->initialbars(false);
274         $table->column_class('id', 'numcol');
275         $table->column_class('credits', 'numcol');
276         $table->column_class('rcounts', 'numcol');
277      //   $table->column_class('rpercent', 'numcol');
278         $table->column_class('facility', 'numcol');
279         $table->column_class('qsd', 'numcol');
280         $table->column_class('disc_index', 'numcol');
281         $table->column_class('disc_coeff', 'numcol');
283         $table->column_suppress('id');
284         $table->column_suppress('qname');
285         $table->column_suppress('facility');
286         $table->column_suppress('qsd');
287         $table->column_suppress('disc_index');
288         $table->column_suppress('disc_coeff');
290         $table->set_attribute('cellspacing', '0');
291         $table->set_attribute('id', 'itemanalysis');
292         $table->set_attribute('class', 'generaltable generalbox');
294         // Start working -- this is necessary as soon as the niceties are over
295         $table->setup();
297         $tablesort = $table->get_sql_sort();
298         $sorts = explode(",",trim($tablesort));
299         if ($tablesort and is_array($sorts)) {
300             $sortindex = array();
301             $sortorder = array ();
302             foreach ($sorts as $sort) {
303                 $data = explode(" ",trim($sort));
304                 $sortindex[] = trim($data[0]);
305                 $s = trim($data[1]);
306                 if ($s=="ASC") {
307                     $sortorder[] = SORT_ASC;
308                 } else {
309                     $sortorder[] = SORT_DESC;
310                 }
311             }
312             if (count($sortindex)>0) {
313                 $sortindex[] = "id";
314                 $sortorder[] = SORT_ASC;
315                 foreach($questions as $qid => $row){
316                     $index1[$qid] = $row[$sortindex[0]];
317                     $index2[$qid] = $row[$sortindex[1]];
318                 }
319                 array_multisort($index1, $sortorder[0], $index2, $sortorder[1], $questions);
320             }
321         }
323         $format_options = new stdClass;
324         $format_options->para = false;
325         $format_options->noclean = true;
326         $format_options->newlines = false;
328         // Now it is time to page the data, simply slice the keys in the array
329         $table->pagesize($pagesize, count($questions));
330         $start = $table->get_page_start();
331         $pagequestions = array_slice(array_keys($questions), $start, $pagesize);
333         foreach($pagequestions as $qnum) {
334             $q = $questions[$qnum];
335             $qid = $q['id'];
336             $question = get_record('question', 'id', $qid);
337             $qnumber = " (".link_to_popup_window('/question/question.php?id='.$qid,'editquestion', $qid, 450, 550, get_string('edit'), 'none', true ).") ";
338             $qname = '<div class="qname">'.format_text($question->name." :  ", $question->questiontextformat, $format_options, $quiz->course).'</div>';
339             $qicon = print_question_icon($question, true);
340             $qreview = quiz_question_preview_button($quiz, $question);
341             $qtext = format_text($question->questiontext, $question->questiontextformat, $format_options, $quiz->course);
342             $qquestion = $qname."\n".$qtext."\n";
344             $responses = array();
345             foreach ($q['responses'] as $aid=>$resp){
346                 $response = new stdClass;
347                 if ($q['credits'][$aid] <= 0) {
348                     $qclass = 'uncorrect';
349                 } elseif ($q['credits'][$aid] == 1) {
350                     $qclass = 'correct';
351                 } else {
352                     $qclass = 'partialcorrect';
353                 }
354                 $response->credit = '<span class="'.$qclass.'">('.format_float($q['credits'][$aid],2).') </span>';
355                 $response->text = '<span class="'.$qclass.'">'.format_text($resp, FORMAT_MOODLE, $format_options, $quiz->course).' </span>';
356                 $count = $q['rcounts'][$aid].'/'.$q['count'];
357                 $response->rcount = $count;
358                 $response->rpercent =  $q['rpercent'][$aid];
359                 $responses[] = $response;
360             }
362             $facility = format_float($q['facility']*100,0)."%";
363             $qsd = format_float($q['qsd'],3);
364             $di = format_float($q['disc_index'],2);
365             $dc = format_float($q['disc_coeff'],2);
367             $response = array_shift($responses);
368             $table->add_data(array($qnumber."\n<br />".$qicon."\n ".$qreview, $qquestion, $response->text, $response->credit, $response->rcount, $response->rpercent, $facility, $qsd, $di, $dc));
369             foreach($responses as $response) {
370                 $table->add_data(array('', '', $response->text, $response->credit, $response->rcount, $response->rpercent, '', '', '', ''));
371             }
372         }
373         print_heading_with_help(get_string("analysistitle", "quiz_analysis"),"itemanalysis", "quiz");
375         echo '<div id="tablecontainer">';
376         $table->print_html();
377         echo '</div>';
379         $this->print_options_form($quiz, $cm, $attemptselection, $lowmarklimit, $pagesize);
380         return true;
381     }
384     function print_options_form($quiz, $cm, $attempts, $lowlimit=0, $pagesize=QUIZ_REPORT_DEFAULT_PAGE_SIZE) {
385         global $CFG, $USER;
386         echo '<div class="controls">';
387         echo '<form id="options" action="report.php" method="post">';
388         echo '<fieldset class="invisiblefieldset">';
389         echo '<p class="quiz-report-options">'.get_string('analysisoptions', 'quiz').': </p>';
390         echo '<input type="hidden" name="id" value="'.$cm->id.'" />';
391         echo '<input type="hidden" name="q" value="'.$quiz->id.'" />';
392         echo '<input type="hidden" name="mode" value="analysis" />';
393         echo '<p><label for="menuattemptselection">'.get_string('attemptselection', 'quiz_analysis').'</label> ';
394         $options = array ( QUIZ_ALLATTEMPTS     => get_string("attemptsall", 'quiz_analysis'),
395                            QUIZ_HIGHESTATTEMPT => get_string("attemptshighest", 'quiz_analysis'),
396                            QUIZ_FIRSTATTEMPT => get_string("attemptsfirst", 'quiz_analysis'),
397                            QUIZ_LASTATTEMPT  => get_string("attemptslast", 'quiz_analysis'));
398         choose_from_menu($options, "attemptselection", "$attempts", "");
399         echo '</p>';
400         echo '<p><label for="lowmarklimit">'.get_string('lowmarkslimit', 'quiz_analysis').'</label> ';
401         echo '<input type="text" id="lowmarklimit" name="lowmarklimit" size="1" value="'.$lowlimit.'" /> % </p>';
402         echo '<p><label for="pagesize">'.get_string('pagesize', 'quiz_analysis').'</label> ';
403         echo '<input type="text" id="pagesize" name="pagesize" size="1" value="'.$pagesize.'" /></p>';
404         echo '<p><input type="submit" value="'.get_string('go').'" />';
405         helpbutton("analysisoptions", get_string("analysisoptions",'quiz_analysis'), 'quiz');
406         echo '</p>';
407         echo '</fieldset>';
408         echo '</form>';
409         echo '</div>';
410         echo "\n";
412         echo '<table class="boxaligncenter"><tr>';
413         $options = array();
414         $options["id"] = "$cm->id";
415         $options["q"] = "$quiz->id";
416         $options["mode"] = "analysis";
417         $options['sesskey'] = $USER->sesskey;
418         $options["noheader"] = "yes";
419         if (file_exists("$CFG->libdir/phpdocwriter/lib/include.php")) {
420             echo '<td colspan="5" align="center">';
421         }else {
422             echo '<td colspan="4"  align="center">';
423         }
424         $options["download"] = "HTML";
425         print_single_button('report.php', $options, get_string("downloadhtml", "quiz_analysis"));
426         echo "</td></tr>\n";
427         echo '<tr><td>';
428         $options["download"] = "ODS";
429         print_single_button("report.php", $options, get_string("downloadods"));
430         echo "</td>\n";
431         echo '<td>';
432         $options["download"] = "Excel";
433         print_single_button("report.php", $options, get_string("downloadexcel"));
434         echo "</td>\n";
436         if (file_exists("$CFG->libdir/phpdocwriter/lib/include.php")) {
437             echo '<td>';
438             $options["download"] = "OOo";
439             print_single_button("report.php", $options, get_string("downloadooo", "quiz_analysis"));
440             echo "</td>\n";
441         }
442         echo '<td>';
443         $options["download"] = "CSV";
444         print_single_button('report.php', $options, get_string("downloadtext"));
445         echo "</td>\n";
446         echo "<td>";
447         helpbutton('analysisdownload', get_string('analysisdownload', 'quiz_analysis'), 'quiz');
448         echo "</td>\n";
449         echo '</tr></table>';
452     function get_questions_atttempts_data ($quiz,&$questions,&$attempts,$attemptselection,$usermax){
453         global $CFG, $SESSION, $QTYPES;
454     
455     /// Here we rewiew all attempts and record data to construct the table
456         $statstable = array();
457         $questionarray = array();
458         foreach ($attempts as $attempt) {
459             $questionarray[] = quiz_questions_in_quiz($attempt->layout);
460         }
461         $questionlist = quiz_questions_in_quiz(implode(",", $questionarray));
462         $questionarray = array_unique(explode(",",$questionlist));
463         $questionlist = implode(",", $questionarray);
464         unset($questionarray);
466         foreach ($attempts as $attempt) {
467             switch ($attemptselection) {
468             case QUIZ_ALLATTEMPTS :
469                 $userscore = 0;      // can be anything, not used
470                 break;
471             case QUIZ_HIGHESTATTEMPT :
472                 $userscore = $attempt->sumgrades;
473                 break;
474             case QUIZ_FIRSTATTEMPT :
475                 $userscore = $attempt->timemodified;
476                 break;
477             case QUIZ_LASTATTEMPT :
478                 $userscore = $attempt->timemodified;
479                 break;
480             }
482             if ($attemptselection == QUIZ_ALLATTEMPTS || $userscore == $usermax[$attempt->userid]) {
484             $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
485                    "  FROM {$CFG->prefix}question q,".
486                    "       {$CFG->prefix}quiz_question_instances i".
487                    " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
488                    "   AND q.id IN ($questionlist)";
490             if (!$quizquestions = get_records_sql($sql)) {
491                 print_error('No questions found');
492             }
494             // Load the question type specific information
495             if (!get_question_options($quizquestions)) {
496                 print_error('Could not load question options');
497             }
498             // Restore the question sessions to their most recent states
499             // creating new sessions where required
500             if (!$states = get_question_states($quizquestions, $quiz, $attempt)) {
501                 print_error('Could not restore question sessions');
502             }
503             $numbers = explode(',', $questionlist);
504             $statsrow = array();
505             foreach ($numbers as $i) {
506                 if (!isset($quizquestions[$i]) or !isset($states[$i])) {
507                     continue;
508                 }
509                 $qtype = ($quizquestions[$i]->qtype=='random') ? $states[$i]->options->question->qtype : $quizquestions[$i]->qtype;
510                 if($quizquestions[$i]->qtype =='randomsamatch'){
511                     $quizquestions[$i]->options =$states[$i]->options ;
512                 }   
513                 $q = get_question_responses($quizquestions[$i], $states[$i]);
514                 if (empty($q)){
515                     continue;
516                 }
517                 $qid = $q->id;
518                 if (!isset($questions[$qid])) {
519                     $questions[$qid]['id'] = $qid;
520                     $questions[$qid]['qname'] = $quizquestions[$i]->name;
521                     foreach ($q->responses as $answer => $r) {
522                         $r->count = 0;
523                         $questions[$qid]['responses'][$answer] = $r->answer;
524                         $questions[$qid]['rcounts'][$answer] = 0;
525                         $questions[$qid]['credits'][$answer] = $r->credit;
526                         $statsrow[$qid] = 0;
527                     }
528                 }
529                 $responses = get_question_actual_response($quizquestions[$i], $states[$i]);
530                 foreach ($responses as $resp){
531                     if ($resp) {
532                         if ($key = array_search($resp, $questions[$qid]['responses'])) {
533                             $questions[$qid]['rcounts'][$key]++;
534                         } else {
535                             $test = new stdClass;
536                             $test->responses = $QTYPES[$quizquestions[$i]->qtype]->get_correct_responses($quizquestions[$i], $states[$i]);
537                             if ($key = $QTYPES[$quizquestions[$i]->qtype]->check_response($quizquestions[$i], $states[$i], $test)) {
538                                 $questions[$qid]['rcounts'][$key]++;
539                             } else {
540                                 $questions[$qid]['responses'][] = $resp;
541                                 $questions[$qid]['rcounts'][] = 1;
542                                 $questions[$qid]['credits'][] = 0;
543                             }
544                         }
545                     }
546                 }
547                 $statsrow[$qid] = get_question_fraction_grade($quizquestions[$i], $states[$i]);
548             }
549             $attemptscores[$attempt->id] = $attempt->sumgrades;
550             $statstable[$attempt->id] = $statsrow;
551             }
552         } // Statistics Data table built
554         unset($attempts);
555         unset($quizquestions);
556         unset($states);
558         // now calculate statistics and set the values in the $questions array
559         $top = max($attemptscores);
560         $bottom = min($attemptscores);
561         $gap = ($top - $bottom)/3;
562         $top -=$gap;
563         $bottom +=$gap;
564         foreach ($questions as $qid=>$q) {
565             $questions[$qid] = $this->report_question_stats($q, $attemptscores, $statstable, $top, $bottom);
566             // calculate rpercent
567             foreach($q['responses']as $aid => $resp){
568                 $rpercent =  '('.format_float($q['rcounts'][$aid]/$q['count']*100,0).'%)';
569                 $questions[$qid]['rpercent'][$aid] = $rpercent ;
570             }
571             $SESSION->quiz_analysis_table['question'][$qid]=$questions[$qid] ;
572   
573         }
574                 
575             
576         unset($attemptscores);
577         unset($statstable);
578     }
579     function report_question_stats(&$q, &$attemptscores, &$questionscores, $top, $bottom) {
580         $qstats = array();
581         $qid = $q['id'];
582         $top_scores = $top_count = 0;
583         $bottom_scores = $bottom_count = 0;
584         foreach ($questionscores as $aid => $qrow){
585             if (isset($qrow[$qid])){
586                 $qstats[] =  array($attemptscores[$aid],$qrow[$qid]);
587                 if ($attemptscores[$aid]>=$top){
588                     $top_scores +=$qrow[$qid];
589                     $top_count++;
590                 }
591                 if ($attemptscores[$aid]<=$bottom){
592                     $bottom_scores +=$qrow[$qid];
593                     $bottom_count++;
594                 }
595             }
596         }
597         $n = count($qstats);
598         $sumx = stats_sumx($qstats, array(0,0));
599         $sumg = $sumx[0];
600         $sumq = $sumx[1];
601         $sumx2 = stats_sumx2($qstats, array(0,0));
602         $sumg2 = $sumx2[0];
603         $sumq2 = $sumx2[1];
604         $sumxy = stats_sumxy($qstats, array(0,0));
605         $sumgq = $sumxy[0];
607         $q['count'] = $n;
608         $q['facility'] = $sumq/$n;
609         if ($n<2) {
610             $q['qsd'] = sqrt(($sumq2 - $sumq*$sumq/$n)/($n));
611             $gsd = sqrt(($sumg2 - $sumg*$sumg/$n)/($n));
612         } else {
613             $q['qsd'] = sqrt(($sumq2 - $sumq*$sumq/$n)/($n-1));
614             $gsd = sqrt(($sumg2 - $sumg*$sumg/$n)/($n-1));
615         }
616         $q['disc_index'] = ($top_scores - $bottom_scores)/max($top_count, $bottom_count, 1);
617         $div = $n*$gsd*$q['qsd'];
618         if ($div!=0) {
619             $q['disc_coeff'] = ($sumgq - $sumg*$sumq/$n)/$div;
620         } else {
621             $q['disc_coeff'] = -999;
622         }
623         return $q;
624     }
626     function Export_Excel(&$questions, $filename) {
627         global $CFG;
628         require_once("$CFG->libdir/excellib.class.php");
630     /// Calculate file name
631         $filename .= ".xls";
632     /// Creating a workbook
633         $workbook = new MoodleExcelWorkbook("-");
634     /// Sending HTTP headers
635         $workbook->send($filename);
636     /// Creating the first worksheet
637         $sheettitle = get_string('reportanalysis','quiz_analysis');
638         $myxls =& $workbook->add_worksheet($sheettitle);
639     /// format types
640         $format =& $workbook->add_format();
641         $format->set_bold(0);
642         $formatbc =& $workbook->add_format();
643         $formatbc->set_bold(1);
644         $formatb =& $workbook->add_format();
645         $formatb->set_bold(1);
646         $formaty =& $workbook->add_format();
647         $formaty->set_bg_color('yellow');
648         $formatyc =& $workbook->add_format();
649         $formatyc->set_bg_color('yellow'); //bold text on yellow bg
650         $formatyc->set_bold(1);
651         $formatyc->set_align('center');
652         $formatc =& $workbook->add_format();
653         $formatc->set_align('center');
654         $formatbc->set_align('center');
655         $formatbpct =& $workbook->add_format();
656         $formatbpct->set_bold(1);
657         $formatbpct->set_num_format('0.0%');
658         $formatbrt =& $workbook->add_format();
659         $formatbrt->set_bold(1);
660         $formatbrt->set_align('right');
661         $formatred =& $workbook->add_format();
662         $formatred->set_bold(1);
663         $formatred->set_color('red');
664         $formatred->set_align('center');
665         $formatblue =& $workbook->add_format();
666         $formatblue->set_bold(1);
667         $formatblue->set_color('blue');
668         $formatblue->set_align('center');
669     /// Here starts workshhet headers
670         $myxls->write_string(0,0,$sheettitle,$formatb);
672         $headers = array(strip_tags(get_string('row','quiz_analysis')),strip_tags(get_string('qidtitle','quiz_analysis')), 
673                         strip_tags(get_string('index','quiz_analysis')),strip_tags(get_string('qtypetitle','quiz_analysis')),
674                         strip_tags(get_string('qnametitle','quiz_analysis')), strip_tags(get_string('qtexttitle','quiz_analysis')),
675                         strip_tags(get_string('responsestitle','quiz_analysis')), strip_tags(get_string('rfractiontitle','quiz_analysis')),
676                         strip_tags(get_string('rcounttitle','quiz_analysis')), 
677                         strip_tags(get_string('rcounttitle','quiz_analysis').'/'.get_string('qcounttitle','quiz_analysis')),
678                         strip_tags(get_string('rpercenttitle','quiz_analysis')), strip_tags(get_string('qcounttitle','quiz_analysis')),
679                         strip_tags(get_string('facilitytitle','quiz_analysis')), strip_tags(get_string('stddevtitle','quiz_analysis')),
680                         strip_tags(get_string('dicsindextitle','quiz_analysis')), strip_tags(get_string('disccoefftitle','quiz_analysis')));
682         $col = 0;
683         foreach ($headers as $item) {
684             $myxls->write(2,$col,$item,$formatbc);
685             $col++;
686         }
688         $row = 3;
689         
690         foreach($questions as $q) {
691             $r = $row -2;
692             $rows = $this->print_row_stats_data($q,$r);
693             foreach($rows as $rowdata){
694                 $col = 0;
695                 foreach($rowdata as $item){
696                     $myxls->write($row,$col,$item,$format);
697                     $col++;
698                 }
699                 $row++;
700             }
701         }
702     /// Close the workbook
703         $workbook->close();
705         exit;
706     }
709     function Export_ODS(&$questions, $filename) {
710         global $CFG;
711         require_once("$CFG->libdir/odslib.class.php");
713     /// Calculate file name
714         $filename .= ".ods";
715     /// Creating a workbook
716         $workbook = new MoodleODSWorkbook("-");
717     /// Sending HTTP headers
718         $workbook->send($filename);
719     /// Creating the first worksheet
720         $sheettitle = get_string('reportanalysis','quiz_analysis');
721         $myxls =& $workbook->add_worksheet($sheettitle);
722     /// format types
723         $format =& $workbook->add_format();
724         $format->set_bold(0);
725         $formatbc =& $workbook->add_format();
726         $formatbc->set_bold(1);
727         $formatb =& $workbook->add_format();
728         $formatb->set_bold(1);
729         $formaty =& $workbook->add_format();
730         $formaty->set_bg_color('yellow');
731         $formatyc =& $workbook->add_format();
732         $formatyc->set_bg_color('yellow'); //bold text on yellow bg
733         $formatyc->set_bold(1);
734         $formatyc->set_align('center');
735         $formatc =& $workbook->add_format();
736         $formatc->set_align('center');
737         $formatbc->set_align('center');
738         $formatbpct =& $workbook->add_format();
739         $formatbpct->set_bold(1);
740         $formatbpct->set_num_format('0.0%');
741         $formatbrt =& $workbook->add_format();
742         $formatbrt->set_bold(1);
743         $formatbrt->set_align('right');
744         $formatred =& $workbook->add_format();
745         $formatred->set_bold(1);
746         $formatred->set_color('red');
747         $formatred->set_align('center');
748         $formatblue =& $workbook->add_format();
749         $formatblue->set_bold(1);
750         $formatblue->set_color('blue');
751         $formatblue->set_align('center');
752     /// Here starts workshhet headers
753         $myxls->write_string(0,0,$sheettitle,$formatb);
754         $headers = array(strip_tags(get_string('row','quiz_analysis')),strip_tags(get_string('qidtitle','quiz_analysis')), 
755                         strip_tags(get_string('index','quiz_analysis')),strip_tags(get_string('qtypetitle','quiz_analysis')),
756                         strip_tags(get_string('qnametitle','quiz_analysis')), strip_tags(get_string('qtexttitle','quiz_analysis')),
757                         strip_tags(get_string('responsestitle','quiz_analysis')), strip_tags(get_string('rfractiontitle','quiz_analysis')),
758                         strip_tags(get_string('rcounttitle','quiz_analysis')), 
759                         strip_tags(get_string('rcounttitle','quiz_analysis').'/'.get_string('qcounttitle','quiz_analysis')),
760                         strip_tags(get_string('rpercenttitle','quiz_analysis')), strip_tags(get_string('qcounttitle','quiz_analysis')),
761                         strip_tags(get_string('facilitytitle','quiz_analysis')), strip_tags(get_string('stddevtitle','quiz_analysis')),
762                         strip_tags(get_string('dicsindextitle','quiz_analysis')), strip_tags(get_string('disccoefftitle','quiz_analysis')));
764         $col = 0;
765         foreach ($headers as $item) {
766             $myxls->write(2,$col,$item,$formatbc);
767             $col++;
768         }
770         $row = 3;
771         foreach($questions as $q) {
772             $r = $row -2;
773             $rows = $this->print_row_stats_data($q,$r);
774             foreach($rows as $rowdata){
775                 $col = 0;
776                 foreach($rowdata as $item){
777                     $myxls->write($row,$col,$item,$format);
778                     $col++;
779                 }
780                 $row++;
781             }
782         }
783     /// Close the workbook
784         $workbook->close();
786         exit;
787     }
789     function Export_CSV(&$questions, $filename) {
791         $headers = array(strip_tags(get_string('row','quiz_analysis')),strip_tags(get_string('qidtitle','quiz_analysis')), 
792                         strip_tags(get_string('index','quiz_analysis')),strip_tags(get_string('qtypetitle','quiz_analysis')),
793                         strip_tags(get_string('qnametitle','quiz_analysis')), strip_tags(get_string('qtexttitle','quiz_analysis')),
794                         strip_tags(get_string('responsestitle','quiz_analysis')), strip_tags(get_string('rfractiontitle','quiz_analysis')),
795                         strip_tags(get_string('rcounttitle','quiz_analysis')), 
796                         strip_tags(get_string('rcounttitle','quiz_analysis').'/'.get_string('qcounttitle','quiz_analysis')),
797                         strip_tags(get_string('rpercenttitle','quiz_analysis')), strip_tags(get_string('qcounttitle','quiz_analysis')),
798                         strip_tags(get_string('facilitytitle','quiz_analysis')), strip_tags(get_string('stddevtitle','quiz_analysis')),
799                         strip_tags(get_string('dicsindextitle','quiz_analysis')), strip_tags(get_string('disccoefftitle','quiz_analysis')));
801         $text = implode("\t", $headers)." \n";
803         $filename .= ".txt";
805         header("Content-Type: application/download\n");
806         header("Content-Disposition: attachment; filename=\"$filename\"");
807         header("Expires: 0");
808         header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
809         header("Pragma: public");
811         echo $text;
812         
813         $row = 1;
814         foreach($questions as $q) {
815             $rows = $this->print_row_stats_data_CSV($q,$row);
816             foreach($rows as $rowdata){
817                 $text = implode("\t", $rowdata);
818                 echo $text." \n";
819                 $row++;
820             }
821         }
822         exit;
823     }
824         function print_row_stats_data_CSV(&$q,&$index) {
825         $qid = $q['id'];
826         $question = get_record('question', 'id', $qid);
827         
828         $options = new stdClass;
829         $options->para = false;
830         $options->noclean = true;
831         $options->newlines = false;
832         $qtype = $question->qtype;
833         $qname = format_text($question->name, FORMAT_MOODLE, $options);
834         $trans = str_replace('\t',' ',$question->questiontext);
835         $trans = substr($trans,0,390);
836         $order = array("\r\n", "\n", "\r");
837         $trans = str_replace($order,' ',$trans);
838        // $trans = explode(',',$trans);
839         
840        // $qtext = format_text(implode('__',$trans), FORMAT_MOODLE, $options);
841         
842         $qtext = format_text($trans, FORMAT_MOODLE, $options);
843         $responses = array();
844         foreach ($q['responses'] as $aid=>$resp){
845             $response = new stdClass;
846             if ($q['credits'][$aid] <= 0) {
847                 $qclass = 'uncorrect';
848             } elseif ($q['credits'][$aid] == 1) {
849                 $qclass = 'correct';
850             } else {
851                 $qclass = 'partialcorrect';
852             }
853             $response->credit = " (".format_float($q['credits'][$aid],2).") ";
854             $response->text = format_text("$resp", FORMAT_MOODLE, $options);
855             $count = $q['rcounts'][$aid].'/'.$q['count'];
856             $response->count = $q['rcounts'][$aid];
857             $response->rcount = $count;
858             $response->rpercent =  format_float($q['rcounts'][$aid]/$q['count']*100,0).'%';
859             $responses[] = $response;
860         }
861         $count = format_float($q['count'],0);
862         $facility = format_float($q['facility']*100,0);
863         $qsd = format_float($q['qsd'],4);
864         $di = format_float($q['disc_index'],3);
865         $dc = format_float($q['disc_coeff'],3);
866         $result = array();
867         $response = array_shift($responses);
868         $indexrep= 0;
869         $result[] = array($index,$qid,$indexrep, $qtype, $qname, $qtext,' ', ' ', ' ', ' ',' ', $count, $facility, $qsd, $di, $dc);
870         $index++ ;
871         $indexrep++;
872         $result[] = array($index,$qid,$indexrep, $qtype, $qname, substr($qtext,0,250) , $response->text, $response->credit, $response->count,$response->rcount, $response->rpercent, $count, ' ', ' ', ' ', ' ');
873         $index++ ;
874         $indexrep++;
875         foreach($responses as $response){
876             $result[] = array($index,$qid,$indexrep,$qtype,$qname,substr($qtext,0,250) , $response->text, $response->credit,$response->count, $response->rcount, $response->rpercent, $count, ' ', ' ', ' ', ' ');
877         $index++ ;
878         $indexrep++;
879         }
880         return $result;
881     }
882     function Export_HTML(&$questions, $filename) {
883         $headers = array();
884         $headers[] = '<font color="blue"><strong>'.get_string('qidtitle','quiz_analysis').'</strong></font>';
885         $headers[] = '<font color="blue"><strong>'.get_string('qtypetitle','quiz_analysis').'</strong></font>';
886         $headers[] = '<font color="green"><strong>'.get_string('qnametitle','quiz_analysis').':</strong></font>'.'<br />'.'<font color="blue"><strong>'.get_string('qtexttitle','quiz_analysis').'</strong></font>';
887         $headers[] = '<font color="blue"><strong>'.get_string('responsestitle','quiz_analysis').'</strong></font>'; 
888         $headers[] = '<font color="blue"><strong>'.get_string('rfractiontitle','quiz_analysis').'</strong></font>';
889         $headers[] = '<font color="blue"><strong>'.get_string('rcounttitle','quiz_analysis').' / '.get_string('qcounttitle','quiz_analysis').'</strong></font>';
890         $headers[] = '<font color="blue"><strong>'.get_string('rpercenttitle','quiz_analysis').'</strong></font>';
891         $headers[] = '<font color="blue"><strong>'.get_string('facilitytitle','quiz_analysis').'</strong></font>'; 
892         $headers[] = '<font color="blue"><strong>'.get_string('stddevtitle','quiz_analysis').'</strong></font>';
893         $headers[] = '<font color="blue"><strong>'.get_string('dicsindextitle','quiz_analysis').'</strong></font>'; 
894         $headers[] = '<font color="blue"><strong>'.get_string('disccoefftitle','quiz_analysis').'</strong></font>';
895         $text = implode("</td><td>", $headers)." \n";
897         $filename .= ".html";
898         header("Content-Type: application/download\n");
899         header("Content-Disposition: attachment; filename=\"$filename\"");
900         header("Expires: 0");
901         header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
902         header("Pragma: public");
903         print_header();        
904         echo '<table border = "1" bordercolor="#808080" cellspacing = "0" >';
905         echo '<tr><td>'.$text.'</td></tr>';
906          foreach($questions as $q) {
907             $rows = $this->print_row_stats_data_html($q);
908             foreach($rows as $row){
909                 $text = implode("</td><td>", $row);
910                 echo '<tr><td>'.$text.'</td></tr>';
911                 //echo $text." \n";
912             }
913         }
914         echo '</table></div>';
915         echo '</div></body></html>';
916         exit;
917     }
918     function print_row_stats_data_html(&$q) {
919         global $QTYPE_MENU ;
920         $format_options = new stdClass;
921         $format_options->para = false;
922         $format_options->noclean = true;
923         $format_options->newlines = false;
925             $qid = $q['id'];
926             $question = get_record('question', 'id', $qid);
927             $qnumber = "".$qid."";
928             $qname = '<font color="green">'.format_text($question->name, $question->questiontextformat, $format_options).':</font><br />';
929             $qicon = '';//print_question_icon($question, true);
930             $qreview = ''; //quiz_question_preview_button($quiz, $question);
931             $qtext = format_text($question->questiontext, $question->questiontextformat, $format_options);
932             $qquestion = '<table><tr><td>'.$qname."\n".$qtext."\n".'</td></tr></table>';
933             $qtype = $QTYPE_MENU[$question->qtype];
935             $responses = array();
936             foreach ($q['responses'] as $aid=>$resp){
937                 $response = new stdClass;
938                 if ($q['credits'][$aid] <= 0) {
939                     $qclass = 'red';//'uncorrect';
940                 } elseif ($q['credits'][$aid] == 1) {
941                     $qclass = 'blue';//'correct';
942                 } else {
943                     $qclass = 'green';//'partialcorrect';
944                 }
945                 $response->credit = '<font color="'.$qclass.'">('.format_float($q['credits'][$aid],2).')</font>';
946                 if ($response->text ='') {
947                     $response->text ='&nbsp;';
948                 }
949                 $response->text = '<font color="'.$qclass.'">'.format_text($resp, FORMAT_MOODLE, $format_options).'</font>';
950                 $count = '&nbsp;'.$q['rcounts'][$aid].'&nbsp;/&nbsp; '.$q['count'];
951                 $response->rcount = $count;
952                 $response->rpercent =  $q['rpercent'][$aid];
953                 $responses[] = $response;
954             }
956             $facility = format_float($q['facility']*100,0)."%";
957             $qsd = format_float($q['qsd'],3);
958             $di = format_float($q['disc_index'],2);
959             $dc = format_float($q['disc_coeff'],2);
961             $response = array_shift($responses);
962             $result[] =(array($qnumber,$qtype, $qquestion, $response->text, $response->credit, $response->rcount, $response->rpercent, $facility, $qsd, $di, $dc));
963             foreach($responses as $response) {
964                 $result[]=(array('&nbsp;','&nbsp;','&nbsp;',$response->text, $response->credit, $response->rcount, $response->rpercent, '&nbsp; ', '&nbsp; ', '&nbsp; ', '&nbsp; '));
965             }
966             return $result;
967           }
968     function print_row_stats_data(&$q, &$index) {
969         $qid = $q['id'];
970         $question = get_record('question', 'id', $qid);
972         $options = new stdClass;
973         $options->para = false;
974         $options->noclean = true;
975         $options->newlines = false;
977         $qtype = $question->qtype;
979         $qname = format_text($question->name, FORMAT_MOODLE, $options);
980         $qtext = format_text($question->questiontext, FORMAT_MOODLE, $options);
982         $responses = array();
983         foreach ($q['responses'] as $aid=>$resp){
984             $response = new stdClass;
985             if ($q['credits'][$aid] <= 0) {
986                 $qclass = 'uncorrect';
987             } elseif ($q['credits'][$aid] == 1) {
988                 $qclass = 'correct';
989             } else {
990                 $qclass = 'partialcorrect';
991             }
992             $response->credit = " (".format_float($q['credits'][$aid],2).") ";
993             $response->text = format_text("$resp", FORMAT_MOODLE, $options);
994             $count = $q['rcounts'][$aid].'/'.$q['count'];
995             $response->count = $q['rcounts'][$aid];
996             $response->rcount = $count;
997 //            $response->rpercent =  format_float($q['rcounts'][$aid]/$q['count']*100,0).'%';
998             $response->rpercent =  $q['rcounts'][$aid]/$q['count']*100.0;
999             $responses[] = $response;
1000         }
1001         $count = format_float($q['count'],0);
1002         $facility = format_float($q['facility']*100,0);
1003         $qsd = format_float($q['qsd'],4);
1004         $di = format_float($q['disc_index'],3);
1005         $dc = format_float($q['disc_coeff'],3);
1007         $result = array();
1008         $response = array_shift($responses);
1009         $indexrep= 0;
1010         $result[] = array($index,$qid,$indexrep, $qtype, $qname, $qtext,' ', ' ', ' ', ' ', ' ',$count, $facility, $qsd, $di, $dc);
1011         $index++ ;
1012         $indexrep++;
1013         $result[] = array($index,$qid,$indexrep, $qtype, $qname, substr($qtext,0,250) , $response->text, $response->credit, $response->count,$response->rcount, $response->rpercent, $count, ' ', ' ', ' ', ' ');
1014         $index++ ;
1015         $indexrep++;
1016         foreach($responses as $response){
1017             $result[] = array($index,$qid,$indexrep,$qtype,$qname,substr($qtext,0,250) , $response->text, $response->credit, $response->count,$response->rcount, $response->rpercent, $count, ' ', ' ', ' ', ' ');
1018         $index++ ;
1019         $indexrep++;
1020         }
1021         return $result;
1022     }
1025 define('QUIZ_ALLATTEMPTS', 0);
1026 define('QUIZ_HIGHESTATTEMPT', 1);
1027 define('QUIZ_FIRSTATTEMPT', 2);
1028 define('QUIZ_LASTATTEMPT', 3);
1030 function stats_sumx($data, $initsum){
1031     $accum = $initsum;
1032     foreach ($data as $v) {
1033         $accum[0] += $v[0];
1034         $accum[1] += $v[1];
1035     }
1036     return $accum;
1039 function stats_sumx2($data, $initsum){
1040     $accum = $initsum;
1041     foreach ($data as $v) {
1042         $accum[0] += $v[0]*$v[0];
1043         $accum[1] += $v[1]*$v[1];
1044     }
1045     return $accum;
1048 function stats_sumxy($data, $initsum){
1049     $accum = $initsum;
1050     foreach ($data as $v) {
1051         $accum[0] += $v[0]*$v[1];
1052         $accum[1] += $v[1]*$v[0];
1053     }
1054     return $accum;
1057 ?>