$string['lastcalculated'] = 'Last calculated $a->lastcalculated ago there have been $a->count attempts since then.';
$string['recalculatenow'] = 'Recalculate now';
$string['detailedanalysis'] = 'More detailed analysis of the responses to this question';
-$string['errordeletingquizstats'] = 'Error deleting old quiz_statistics records.';
-$string['errordeletingqstats'] = 'Error deleting old quiz_question_statistics records.';
+$string['errordeleting'] = 'Error deleting old $a records.';
$string['questionname'] = 'Question Name';
$string['questiontype'] = 'Question Type';
$string['positions'] = 'Position(s)';
$string['questionstatistics'] = 'Question statistics';
$string['analysisofresponses'] = 'Analysis of responses';
$string['statisticsreportgraph'] = 'Statistics for question positions';
+$string['response'] = 'Answer';
+$string['optiongrade'] = 'Partial credit';
+$string['count'] = 'Count';
+$string['frequency'] = 'Frequency';
+$string['backtoquizreport'] = 'Back to main statistics report page.';
+$string['analysisofresponsesfor'] = 'Analysis of responses for $a.';
+$string['downloadeverything'] = 'Download full report as';
+
?>
\ No newline at end of file
return $this->download;
}
- function export_class_instance(){
- if (is_null($this->exportclass) && !empty($this->download)){
+ function export_class_instance(&$exportclass=null){
+ if (!is_null($exportclass)){
+ $this->started_output = true;
+ $this->exportclass =& $exportclass;
+ $this->exportclass->table =& $this;
+ } elseif (is_null($this->exportclass) && !empty($this->download)){
$classname = 'table_'.$this->download.'_export_format';
$this->exportclass = new $classname($this);
if (!$this->exportclass->document_started()){
* This function is not part of the public api.
*/
function print_initials_bar(){
- if (($this->sess->i_last || $this->sess->i_first || $this->use_initials)
+ if ((!empty($this->sess->i_last) || !empty($this->sess->i_first) || $this->use_initials)
&& isset($this->columns['fullname'])) {
$strall = get_string('all');
<html xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en" lang="en">
+<head>
<style type="text/css">/*<![CDATA[*/
.flexible th {
.bold {
font-weight:bold;
}
-
+.mdl-align {
+ text-align:center;
+}
/*]]>*/</style>
+<title>$filename</title>
+</head>
<body>
EOF;
$this->documentstarted = true;
$this->table->finish_html();
}
function finish_document(){
- echo '</body>';
+ echo "</body>\n</html>";
exit;
}
}
return $gradedstates;
}
}
-function quiz_report_index_by_keys($datum, $keys){
+/**
+ * Takes an array of objects and constructs a multidimensional array keyed by
+ * the keys it finds on the object.
+ * @param array $datum an array of objects with properties on the object
+ * including the keys passed as the next param.
+ * @param array $keys Array of strings with the names of the properties on the
+ * objects in datum that you want to index the multidimensional array by.
+ * @param boolean $keysunique If there is not only one object for each
+ * combination of keys you are using you should set $keysunique to true.
+ * Otherwise all the object will be added to a zero based array. So the array
+ * returned will have count($keys) + 1 indexs.
+ * @return array multidimensional array properly indexed.
+ */
+function quiz_report_index_by_keys($datum, $keys, $keysunique=true){
if (!$datum){
return $datum;
}
$key = array_shift($keys);
$datumkeyed = array();
foreach ($datum as $data){
- if ($keys){
+ if ($keys || !$keysunique){
$datumkeyed[$data->{$key}][]= $data;
} else {
$datumkeyed[$data->{$key}]= $data;
}
if ($keys){
foreach ($datumkeyed as $datakey => $datakeyed){
- $datumkeyed[$datakey] = quiz_report_index_by_keys($datakeyed, $keys);
+ $datumkeyed[$datakey] = quiz_report_index_by_keys($datakeyed, $keys, $keysunique);
}
}
return $datumkeyed;
}
-
+function quiz_report_unindex($datum){
+ if (!$datum){
+ return $datum;
+ }
+ $datumunkeyed = array();
+ foreach ($datum as $value){
+ if (is_array($value)){
+ $datumunkeyed = array_merge($datumunkeyed, quiz_report_unindex($value));
+ } else {
+ $datumunkeyed[] = $value;
+ }
+ }
+ return $datumunkeyed;
+}
function quiz_get_regraded_qs($attemptidssql, $limitfrom=0, $limitnum=0){
global $CFG, $DB;
if ($attemptidssql && is_array($attemptidssql)){
}
function quiz_report_scale_sumgrades_as_percentage($rawgrade, $quiz, $round = true) {
- if ($quiz->sumgrades) {
+ if ($quiz->sumgrades != 0) {
$grade = $rawgrade * 100 / $quiz->sumgrades;
if ($round) {
$grade = quiz_format_grade($quiz, $grade);
}
} else {
- $grade = 0;
+ return '';
}
return $grade.'%';
}
<?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/quiz/report/statistics/db" VERSION="20080728" COMMENT="XMLDB file for Moodle mod/quiz/report/statistics"
+<XMLDB PATH="mod/quiz/report/statistics/db" VERSION="20080908" COMMENT="XMLDB file for Moodle mod/quiz/report/statistics"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
- <TABLE NAME="quiz_question_statistics" COMMENT="Default comment for the table, please edit me" PREVIOUS="quiz_statistics">
+ <TABLE NAME="quiz_question_statistics" COMMENT="Default comment for the table, please edit me" PREVIOUS="quiz_statistics" NEXT="quiz_question_response_stats">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="quizstatisticsid"/>
<FIELD NAME="quizstatisticsid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="questionid"/>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
+ <TABLE NAME="quiz_question_response_stats" COMMENT="Quiz question responses." PREVIOUS="quiz_question_statistics">
+ <FIELDS>
+ <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="quizstatisticsid"/>
+ <FIELD NAME="quizstatisticsid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="questionid"/>
+ <FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="quizstatisticsid" NEXT="subqid"/>
+ <FIELD NAME="subqid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="questionid" NEXT="aid"/>
+ <FIELD NAME="aid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="subqid" NEXT="response"/>
+ <FIELD NAME="response" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="aid" NEXT="rcount"/>
+ <FIELD NAME="rcount" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="response" NEXT="credit"/>
+ <FIELD NAME="credit" TYPE="number" LENGTH="15" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" DECIMALS="5" PREVIOUS="rcount"/>
+ </FIELDS>
+ <KEYS>
+ <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+ </KEYS>
+ </TABLE>
</TABLES>
</XMLDB>
\ No newline at end of file
$dbman->change_field_type($table, $field);
}
+ if ($result && $oldversion < 2008082600) {
+
+ /// Define table quiz_question_response_stats to be created
+ $table = new xmldb_table('quiz_question_response_stats');
+
+ /// Adding fields to table quiz_question_response_stats
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+ $table->add_field('quizstatisticsid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+ $table->add_field('questionid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+ $table->add_field('anssubqid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+ $table->add_field('response', XMLDB_TYPE_TEXT, 'big', null, null, null, null, null, null);
+ $table->add_field('rcount', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+ $table->add_field('credit', XMLDB_TYPE_NUMBER, '15, 5', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+
+ /// Adding keys to table quiz_question_response_stats
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+ /// Conditionally launch create table for quiz_question_response_stats
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+
+ }
+ if ($result && $oldversion < 2008090500) {
+ //delete all cached results first
+ $result = $result && $DB->delete_records('quiz_statistics');
+ $result = $result && $DB->delete_records('quiz_question_statistics');
+ $result = $result && $DB->delete_records('quiz_question_response_stats');
+ if ($result){
+ /// Define field anssubqid to be dropped from quiz_question_response_stats
+ $table = new xmldb_table('quiz_question_response_stats');
+ $field = new xmldb_field('anssubqid');
+
+ /// Conditionally launch drop field subqid
+ if ($dbman->field_exists($table, $field)) {
+ $dbman->drop_field($table, $field);
+ }
+
+ /// Define field subqid to be added to quiz_question_response_stats
+ $field = new xmldb_field('subqid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null, 'questionid');
+
+ /// Conditionally launch add field subqid
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ /// Define field aid to be added to quiz_question_response_stats
+ $field = new xmldb_field('aid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null, 'subqid');
+
+ /// Conditionally launch add field aid
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+ }
+ }
return $result;
}
var $questions;
var $subquestions = array();
var $randomselectors = array();
+ var $responses = array();
function qstats($questions, $s, $sumgradesavg){
$this->s = $s;
}
function _secondary_states_walker($state, &$stats){
- global $QTYPES;
$gradedifference = ($state->grade - $stats->gradeaverage);
$othergradedifference = (($state->sumgrades - $state->grade) - $stats->othergradeaverage);
$overallgradedifference = $state->sumgrades - $this->sumgradesavg;
$stats->covariancemaxsum += $sortedgradedifference * $sortedothergradedifference;
$stats->covariancewithoverallgradesum += $gradedifference * $overallgradedifference;
+ if ($stats->subquestion){
+ $question =& $this->subquestions[$stats->questionid];
+ } else {
+ $question =& $this->questions[$stats->questionid];
+ }
+ $this->_process_actual_responses($question, $state);
+
+ }
+ function add_response_detail_to_array($responsedetail){
+ $responsedetail->rcount = 1;
+ if (isset($this->responses[$responsedetail->subqid])){
+ if (isset($this->responses[$responsedetail->subqid][$responsedetail->aid])){
+ if (isset($this->responses[$responsedetail->subqid][$responsedetail->aid][$responsedetail->response])){
+ $this->responses[$responsedetail->subqid][$responsedetail->aid][$responsedetail->response]->rcount++;
+ } else {
+ $this->responses[$responsedetail->subqid][$responsedetail->aid][$responsedetail->response] = $responsedetail;
+ }
+ } else {
+ $this->responses[$responsedetail->subqid][$responsedetail->aid] = array($responsedetail->response => $responsedetail);
+ }
+ } else {
+ $this->responses[$responsedetail->subqid] = array();
+ $this->responses[$responsedetail->subqid][$responsedetail->aid] = array($responsedetail->response => $responsedetail);
+ }
}
+ /**
+ * Get the data for the individual question response analysis table.
+ */
+ function _process_actual_responses($question, $state){
+ global $QTYPES;
+ if ($question->qtype != 'random' &&
+ $QTYPES[$question->qtype]->show_analysis_of_responses()){
+ $restoredstate = clone($state);
+ restore_question_state($question, $restoredstate);
+ $responsedetails = $QTYPES[$question->qtype]->get_actual_response_details($question, $restoredstate);
+ foreach ($responsedetails as $responsedetail){
+ $responsedetail->questionid = $question->id;
+ $this->add_response_detail_to_array($responsedetail);
+ }
+ }
+ }
function _initial_question_walker(&$stats){
$stats->gradeaverage = $stats->totalgrades / $stats->s;
}
function process_states(){
+ global $DB;
+ set_time_limit(0);
$subquestionstats = array();
foreach ($this->states as $state){
$this->_initial_states_walker($state, $this->questions[$state->question]->_stats);
foreach (array_keys($this->subquestions) as $qid){
$this->subquestions[$qid]->_stats = $subquestionstats[$qid];
$this->subquestions[$qid]->_stats->questionid = $qid;
+ $this->subquestions[$qid]->maxgrade = $this->subquestions[$qid]->_stats->maxgrade;
$this->_initial_question_walker($this->subquestions[$qid]->_stats);
if ($subquestionstats[$qid]->differentweights){
notify(get_string('erroritemappearsmorethanoncewithdifferentweight', 'quiz_statistics', $this->subquestions[$qid]->name));
$this->questions[$qid]->_stats->effectiveweight = null;
}
}
+ $this->responses = quiz_report_unindex($this->responses);
}
/**
* Needed by quiz stats calculations.
* Display the report.
*/
function display($quiz, $cm, $course) {
- global $CFG, $DB;
+ global $CFG, $DB, $QTYPES;
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
$download = optional_param('download', '', PARAM_ALPHA);
+ $everything = optional_param('everything', 0, PARAM_BOOL);
$recalculate = optional_param('recalculate', 0, PARAM_BOOL);
//pass the question id for detailed analysis question
$qid = optional_param('qid', 0, PARAM_INT);
if ($todelete = $DB->get_records_menu('quiz_statistics', array('quizid' => $quiz->id, 'groupid'=> (int)$currentgroup, 'allattempts'=>$useallattempts))){
list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete));
if (!$DB->delete_records_select('quiz_statistics', "id $todeletesql", $todeleteparams)){
- print_error('errordeletingquizstats', 'quiz_statistics');
+ print_error('errordeleting', 'quiz_statistics', '', 'quiz_statistics');
}
if (!$DB->delete_records_select('quiz_question_statistics', "quizstatisticsid $todeletesql", $todeleteparams)){
- print_error('errordeletingqstats', 'quiz_statistics');
+ print_error('errordeleting', 'quiz_statistics', '', 'quiz_question_statistics');
+ }
+ if (!$DB->delete_records_select('quiz_question_response_stats', "quizstatisticsid $todeletesql", $todeleteparams)){
+ print_error('errordeleting', 'quiz_statistics', '', 'quiz_question_response_stats');
}
}
redirect($reporturl->out());
}
if (!$qid){//main page
- $this->output_quiz_stats_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl);
- $this->output_question_stats_table($s, $questions, $subquestions);
- $imageurl = $CFG->wwwroot.'/mod/quiz/report/statistics/statistics_graph.php?id='.$quizstats->id;
- print_heading(get_string('statisticsreportgraph', 'quiz_statistics'));
- echo '<div class="mdl-align"><img src="'.$imageurl.'" alt="'.get_string('statisticsreportgraph', 'quiz_statistics').'" /></div>';
+ $this->output_quiz_info_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl, $everything);
+ $this->output_quiz_structure_analysis_table($s, $questions, $subquestions);
+ if (!$this->table->is_downloading() || ($everything && $this->table->is_downloading() == 'xhtml')){
+ if ($s > 1){
+ $imageurl = $CFG->wwwroot.'/mod/quiz/report/statistics/statistics_graph.php?id='.$quizstats->id;
+ print_heading(get_string('statisticsreportgraph', 'quiz_statistics'));
+ echo '<div class="mdl-align"><img src="'.$imageurl.'" alt="'.get_string('statisticsreportgraph', 'quiz_statistics').'" /></div>';
+ }
+ }
+ if ($this->table->is_downloading()){
+ if ($everything){
+ foreach ($questions as $question){
+ if ($question->qtype != 'random' && $QTYPES[$question->qtype]->show_analysis_of_responses()){
+ $this->output_individual_question_data($quiz, $question, $reporturl, $quizstats);
+ } elseif (!empty($question->_stats->subquestions)) {
+ $subitemstodisplay = explode(',', $question->_stats->subquestions);
+ foreach ($subitemstodisplay as $subitemid){
+ $this->output_individual_question_data($quiz, $subquestions[$subitemid], $reporturl, $quizstats);
+ }
+ }
+ }
+ $exportclassinstance =& $this->table->export_class_instance();
+ } else {
+ $this->table->finish_output();
+ }
+ }
+ if ($this->table->is_downloading() && $everything){
+ $exportclassinstance->finish_document();
+ }
} else {//individual question page
$thisquestion = false;
if (isset($questions[$qid])){
} else {
print_error('questiondoesnotexist', 'question');
}
- $this->output_question_info_table($quiz, $thisquestion);
+ $this->output_individual_question_data($quiz, $thisquestion, $reporturl, $quizstats);
}
return true;
}
- function output_question_info_table($quiz, $question){
- $datumfromtable = $this->table->format_row($question);
-
- $questioninfotable = new object();
- $questioninfotable->align = array('center', 'center');
- $questioninfotable->width = '60%';
- $questioninfotable->class = 'generaltable titlesleft';
-
- $questioninfotable->data = array();
- $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
- $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'), $question->name.' '.$datumfromtable['actions']);
- $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'), $question->qtype.' '.$datumfromtable['icon']);
- $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'), $question->_stats->positions);
-
- $questionstatstable = new object();
- $questionstatstable->align = array('center', 'center');
- $questionstatstable->width = '60%';
- $questionstatstable->class = 'generaltable titlesleft';
-
- unset($datumfromtable['number']);
- unset($datumfromtable['icon']);
- $actions = $datumfromtable['actions'];
- unset($datumfromtable['actions']);
- unset($datumfromtable['name']);
- $labels = array('s' => get_string('attempts', 'quiz_statistics'),
- 'facility' => get_string('facility', 'quiz_statistics'),
- 'sd' => get_string('standarddeviationq', 'quiz_statistics'),
- 'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
- 'intended_weight'=> get_string('intended_weight', 'quiz_statistics'),
- 'effective_weight'=> get_string('effective_weight', 'quiz_statistics'),
- 'discrimination_index'=> get_string('discrimination_index', 'quiz_statistics'),
- 'discriminative_efficiency'=> get_string('discriminative_efficiency', 'quiz_statistics'));
- foreach ($datumfromtable as $item => $value){
- $questionstatstable->data[] = array($labels[$item], $value);
+ function sort_response_details($detail1, $detail2){
+ if ($detail1->credit == $detail2->credit){
+ return strcmp($detail1->answer, $detail2->answer);
}
-
- print_heading(get_string('questioninformation', 'quiz_statistics'));
- print_table($questioninfotable);
-
- print_box(format_text($question->questiontext).$actions, 'boxaligncenter generalbox boxwidthnormal mdl-align');
-
- print_heading(get_string('questionstatistics', 'quiz_statistics'));
- print_table($questionstatstable);
-
- print_heading(get_string('analysisofresponses', 'quiz_statistics'));
-
+ return ($detail1->credit > $detail2->credit) ? -1 : 1;
}
-
- function output_question_stats_table($s, $questions, $subquestions){
+ function sort_answers($answer1, $answer2){
+ if ($answer1->rcount == $answer2->rcount){
+ return strcmp($answer1->response, $answer2->response);
+ } else {
+ return ($answer1->rcount > $answer2->rcount)? -1 : 1;
+ }
+ }
+
+ function output_individual_question_data($quiz, $question, $reporturl, $quizstats){
+ global $CFG, $DB, $QTYPES;
+ require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_question_table.php');
+ $this->qtable = new quiz_report_statistics_question_table($question->id);
+ $downloadtype = $this->table->is_downloading();
+ if (!$this->table->is_downloading()){
+ $datumfromtable = $this->table->format_row($question);
+
+ $questioninfotable = new object();
+ $questioninfotable->align = array('center', 'center');
+ $questioninfotable->width = '60%';
+ $questioninfotable->class = 'generaltable titlesleft';
+
+ $questioninfotable->data = array();
+ $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
+ $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'), $question->name.' '.$datumfromtable['actions']);
+ $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'), $datumfromtable['icon'].' '.get_string($question->qtype,'quiz').' '.$datumfromtable['icon']);
+ $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'), $question->_stats->positions);
+
+ $questionstatstable = new object();
+ $questionstatstable->align = array('center', 'center');
+ $questionstatstable->width = '60%';
+ $questionstatstable->class = 'generaltable titlesleft';
+
+ unset($datumfromtable['number']);
+ unset($datumfromtable['icon']);
+ $actions = $datumfromtable['actions'];
+ unset($datumfromtable['actions']);
+ unset($datumfromtable['name']);
+ $labels = array('s' => get_string('attempts', 'quiz_statistics'),
+ 'facility' => get_string('facility', 'quiz_statistics'),
+ 'sd' => get_string('standarddeviationq', 'quiz_statistics'),
+ 'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
+ 'intended_weight'=> get_string('intended_weight', 'quiz_statistics'),
+ 'effective_weight'=> get_string('effective_weight', 'quiz_statistics'),
+ 'discrimination_index'=> get_string('discrimination_index', 'quiz_statistics'),
+ 'discriminative_efficiency'=> get_string('discriminative_efficiency', 'quiz_statistics'));
+ foreach ($datumfromtable as $item => $value){
+ $questionstatstable->data[] = array($labels[$item], $value);
+ }
+ print_heading(get_string('questioninformation', 'quiz_statistics'));
+ print_table($questioninfotable);
+
+ print_box(format_text($question->questiontext).$actions, 'boxaligncenter generalbox boxwidthnormal mdl-align');
+
+ print_heading(get_string('questionstatistics', 'quiz_statistics'));
+ print_table($questionstatstable);
+
+ } else {
+ $this->qtable->export_class_instance($this->table->export_class_instance());
+ $questiontabletitle = !empty($question->number)?'('.$question->number.') ':'';
+ $questiontabletitle .= "\"{$question->name}\"";
+ $questiontabletitle = "<em>$questiontabletitle</em>";
+ if ($downloadtype == 'xhtml'){
+ $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
+ }
+ $exportclass =& $this->table->export_class_instance();
+ $exportclass->start_table($questiontabletitle);
+ }
+ if ($QTYPES[$question->qtype]->show_analysis_of_responses()){
+ if (!$this->table->is_downloading()){
+ print_heading(get_string('analysisofresponses', 'quiz_statistics'));
+ }
+ $teacherresponses = $QTYPES[$question->qtype]->get_possible_responses($question);
+ $this->qtable->setup($reporturl, $question, count($teacherresponses)>1);
+ if ($this->table->is_downloading()){
+ $exportclass->output_headers($this->qtable->headers);
+ }
+
+ $responses = $DB->get_records('quiz_question_response_stats', array('quizstatisticsid' => $quizstats->id, 'questionid' => $question->id), 'credit DESC, subqid ASC, aid ASC, rcount DESC');
+ $responses = quiz_report_index_by_keys($responses, array('subqid', 'aid'), false);
+ foreach ($responses as $subqid => $response){
+ foreach (array_keys($responses[$subqid]) as $aid){
+ uasort($responses[$subqid][$aid], array('quiz_statistics_report', 'sort_answers'));
+ }
+ if (isset($responses[$subqid]['0'])){
+ $wildcardresponse = new object();
+ $wildcardresponse->answer = '*';
+ $wildcardresponse->credit = 0;
+ $teacherresponses[$subqid][0] = $wildcardresponse;
+ }
+ }
+ $first = true;
+ $subq = 0;
+ foreach ($teacherresponses as $subqid => $tresponsesforsubq){
+ $subq++;
+ $qhaswildcards = $QTYPES[$question->qtype]->has_wildcards_in_responses($question, $subqid);
+ if (!$first){
+ $this->qtable->add_separator();
+ }
+ uasort($tresponsesforsubq, array('quiz_statistics_report', 'sort_response_details'));
+ foreach ($tresponsesforsubq as $aid => $teacherresponse){
+ $teacherresponserow = new object();
+ $teacherresponserow->response = $teacherresponse->answer;
+ $teacherresponserow->rcount = 0;
+ $teacherresponserow->subq = $subq;
+ $teacherresponserow->credit = $teacherresponse->credit;
+ if (isset($responses[$subqid][$aid])){
+ $singleanswer = count($responses[$subqid][$aid])==1 &&
+ ($responses[$subqid][$aid][0]->response == $teacherresponserow->response);
+ if (!$singleanswer && $qhaswildcards){
+ $this->qtable->add_separator();
+ }
+ foreach ($responses[$subqid][$aid] as $response){
+ $teacherresponserow->rcount += $response->rcount;
+ }
+ if ($aid!=0 || $qhaswildcards){
+ $this->qtable->add_data_keyed($this->qtable->format_row($teacherresponserow));
+ }
+ if (!$singleanswer){
+ foreach ($responses[$subqid][$aid] as $response){
+ if (!$downloadtype || $downloadtype=='xhtml'){
+ $indent = ' ';
+ } else {
+ $indent = ' ';
+ }
+ $response->response = ($qhaswildcards?$indent:'').$response->response;
+ $response->subq = $subq;
+ if ((count($responses[$subqid][$aid])<2) || ($response->rcount > ($teacherresponserow->rcount / 10))){
+ $this->qtable->add_data_keyed($this->qtable->format_row($response));
+ }
+ }
+ }
+ } else {
+ $this->qtable->add_data_keyed($this->qtable->format_row($teacherresponserow));
+ }
+ }
+ $first = false;
+ }
+ $this->qtable->finish_output(!$this->table->is_downloading());
+ }
if (!$this->table->is_downloading()){
- print_heading(get_string('quizstructureanalysis', 'quiz_statistics'));
+ $url = $reporturl->out();
+ $text = get_string('backtoquizreport', 'quiz_statistics');
+ print_box("<a href=\"$url\">$text</a>", 'boxaligncenter generalbox boxwidthnormal mdl-align');
}
+ }
+
+ function output_quiz_structure_analysis_table($s, $questions, $subquestions){
if ($s){
+ if (!$this->table->is_downloading()){
+ print_heading(get_string('quizstructureanalysis', 'quiz_statistics'));
+ }
foreach ($questions as $question){
$this->table->add_data_keyed($this->table->format_row($question));
if (!empty($question->_stats->subquestions)){
}
}
- $this->table->finish_output();
+ $this->table->finish_output(!$this->table->is_downloading());
}
}
- function output_quiz_stats_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl){
+ function output_quiz_info_table($course, $cm, $quiz, $quizstats, $usingattemptsstring,
+ $currentgroup, $groupstudents, $useallattempts, $download, $reporturl, $everything){
global $DB;
// Print information on the number of existing attempts
$quizinformationtablehtml = print_heading(get_string('quizinformation', 'quiz_statistics'), '', 2, 'main', true);
get_string('recalculatenow', 'quiz_statistics'), 'post', '', true);
$quizinformationtablehtml .= print_box_end(true);
}
+ $downloadoptions = $this->table->get_download_menu();
+ $quizinformationtablehtml .= '<form action="'. $this->table->baseurl .'" method="post">';
+ $quizinformationtablehtml .= '<div class="mdl-align">';
+ $quizinformationtablehtml .= '<input type="hidden" name="everything" value="1"/>';
+ $quizinformationtablehtml .= '<input type="submit" value="'.get_string('downloadeverything', 'quiz_statistics').'"/>';
+ $quizinformationtablehtml .= choose_from_menu ($downloadoptions, 'download', $this->table->defaultdownloadformat, '', '', '', true);
+ $quizinformationtablehtml .= helpbutton('tableexportformats', get_string('tableexportformats', 'table'), 'moodle', true, false, '', true);
+ $quizinformationtablehtml .= '</div></form>';
}
$quizinformationtablehtml .= print_table($quizinformationtable, true);
if (!$this->table->is_downloading()){
echo $quizinformationtablehtml;
- } else {
+ } elseif ($everything) {
$exportclass =& $this->table->export_class_instance();
if ($download == 'xhtml'){
echo $quizinformationtablehtml;
'FROM '.$fromqa.
'WHERE ' .$whereqa.
'GROUP BY (attempt=1)';
+
if (!$attempttotals = $DB->get_records_sql($sql, $qaparams)){
$s = 0;
+ $usingattemptsstring = '';
} else {
$firstattempt = $attempttotals[1];
$allattempts = new object();
if (!$mediangrades = $DB->get_records_sql_menu($sql, $qaparams, $limitoffset, $limit)){
print_error('errormedian', 'quiz_statistics');
}
- if (count($mediangrades)==1){
- $quizstats->median = array_shift($mediangrades);
- } else {
- $median = array_shift($mediangrades);
- $median += array_shift($mediangrades);
- $quizstats->median = $median /2;
- }
+ $quizstats->median = array_sum($mediangrades) / count($mediangrades);
if ($s>1){
//fetch sum of squared, cubed and power 4d
//differences between grades and mean grade
if ($s>1){
$p = count($qstats->questions);//no of positions
if ($p > 1){
- if ($k2){
+ if (isset($k2)){
$quizstats->cic = (100 * $p / ($p -1)) * (1 - ($qstats->sum_of_grade_variance())/$k2);
$quizstats->errorratio = 100 * sqrt(1-($quizstats->cic/100));
$quizstats->standarderror = ($quizstats->errorratio * $quizstats->standarddeviation / 100);
$params = array('quizid'=>$quiz->id, 'groupid'=>(int)$currentgroup, 'allattempts'=>$useallattempts, 'timemodified'=>$timemodified);
if (!$quizstats = $DB->get_record_select('quiz_statistics', 'quizid = :quizid AND groupid = :groupid AND allattempts = :allattempts AND timemodified > :timemodified', $params, '*', true)){
list($s, $usingattemptsstring, $quizstats, $qstats) = $this->quiz_stats($nostudentsingroup, $quiz->id, $currentgroup, $groupstudents, $questions, $useallattempts);
- $toinsert = (object)((array)$quizstats + $params);
- $toinsert->timemodified = time();
- $quizstats->id = $DB->insert_record('quiz_statistics', $toinsert);
- foreach ($qstats->questions as $question){
- $question->_stats->quizstatisticsid = $quizstats->id;
- $DB->insert_record('quiz_question_statistics', $question->_stats, false, true);
- }
- foreach ($qstats->subquestions as $subquestion){
- $subquestion->_stats->quizstatisticsid = $quizstats->id;
- $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true);
+ if ($s){
+ $toinsert = (object)((array)$quizstats + $params);
+ $toinsert->timemodified = time();
+ $quizstats->id = $DB->insert_record('quiz_statistics', $toinsert);
+ foreach ($qstats->questions as $question){
+ $question->_stats->quizstatisticsid = $quizstats->id;
+ $DB->insert_record('quiz_question_statistics', $question->_stats, false, true);
+ }
+ foreach ($qstats->subquestions as $subquestion){
+ $subquestion->_stats->quizstatisticsid = $quizstats->id;
+ $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true);
+ }
+ foreach ($qstats->responses as $response){
+ $response->quizstatisticsid = $quizstats->id;
+ $DB->insert_record('quiz_question_response_stats', $response, false);
+ }
}
- if (isset($qstats)){
+ if ($qstats){
$questions = $qstats->questions;
$subquestions = $qstats->subquestions;
} else {
$usingattemptsstring = get_string('firstattempts', 'quiz_statistics');
$s = $quizstats->firstattemptscount;
}
+ $subquestions = array();
$questionstats = $DB->get_records('quiz_question_statistics', array('quizstatisticsid'=>$quizstats->id), 'subquestion ASC');
$questionstats = quiz_report_index_by_keys($questionstats, array('subquestion', 'questionid'));
if (1 < count($questionstats)){
foreach (array_keys($subquestions) as $subqid){
$subquestions[$subqid]->_stats = $subquestionstats[$subqid];
}
- } else {
+ } elseif (count($questionstats)) {
$mainquestionstats = $questionstats[0];
- $subquestions = array();
}
- foreach (array_keys($questions) as $qid){
- $questions[$qid]->_stats = $mainquestionstats[$qid];
+ if (count($questionstats)) {
+ foreach (array_keys($questions) as $qid){
+ $questions[$qid]->_stats = $mainquestionstats[$qid];
+ }
}
}
return array($quizstats, $questions, $subquestions, $s, $usingattemptsstring);
}
return array($fromqa, $whereqa, $qaparams);
}
-function quiz_report_safe_divider($dividend, $divisor){
- if ($divisor == 0){
- return null;
- }
- return $dividend / $divisor;
-}
+
?>
$groups = false;
}
if ($quizstatistics->groupid){
- if (!in_array($quizstatistics->groupid, $groups)){
+ if (!in_array($quizstatistics->groupid, array_keys($groups))){
print_error('groupnotamember', 'group');
}
}
$line->parameter['legend_offset'] = 4;
-$line->parameter['bar_size'] = 1.2;
-$line->parameter['bar_spacing'] = 10;
+$line->parameter['bar_size'] = 1;
+
+$line->parameter['zero_axis'] = 'grayEE';
+
$fieldstoplot = array('facility' => get_string('facility', 'quiz_statistics'), 'discriminativeefficiency' => get_string('discriminative_efficiency', 'quiz_statistics'));
$fieldstoplotfactor = array('facility' => 100, 'discriminativeefficiency' => 1);
array('colour' => graph_get_new_colour(), 'bar' => 'fill', 'shadow_offset' => 1, 'legend' => $fieldstoplot[$fieldtoplot]);
}
foreach ($questionstatistics as $questionstatistic){
- $line->x_data[] = $questions[$questionstatistic->questionid]->number;
+ $line->x_data[$questions[$questionstatistic->questionid]->number] = $questions[$questionstatistic->questionid]->number;
foreach (array_keys($fieldstoplot) as $fieldtoplot){
$value = !is_null($questionstatistic->$fieldtoplot)?$questionstatistic->$fieldtoplot:0;
$value = $value * $fieldstoplotfactor[$fieldtoplot];
$line->y_data[$fieldtoplot][$questions[$questionstatistic->questionid]->number] = $value;
}
}
+foreach (array_keys($line->y_data) as $fieldtoplot){
+ ksort($line->y_data[$fieldtoplot]);
+ $line->y_data[$fieldtoplot] = array_values($line->y_data[$fieldtoplot]);
+}
ksort($line->x_data);
+$line->x_data = array_values($line->x_data);
$max = 0;
$min = 0;
foreach (array_keys($fieldstoplot) as $fieldtoplot){
ksort($line->y_data[$fieldtoplot]);
$line->y_data[$fieldtoplot] = array_values($line->y_data[$fieldtoplot]);
$max = (max($line->y_data[$fieldtoplot])> $max)? max($line->y_data[$fieldtoplot]): $max;
- $min = (min($line->y_data[$fieldtoplot])> $min)? min($line->y_data[$fieldtoplot]): $min;
+ $min = (min($line->y_data[$fieldtoplot])< $min)? min($line->y_data[$fieldtoplot]): $min;
}
$line->y_order = array_keys($fieldstoplot);
+$gridresolution = 10;
+
+$max = ceil($max / $gridresolution) * $gridresolution;
+$min = floor($min / $gridresolution) * $gridresolution;
+
+$gridlines = ceil(($max - $min) / $gridresolution);
+
+
+$line->parameter['y_axis_gridlines'] = $gridlines+1;
-$line->parameter['y_min_left'] = $min; // start at 0
+$line->parameter['y_min_left'] = $min;
$line->parameter['y_max_left'] = $max;
$line->parameter['y_decimal_left'] = 0;
--- /dev/null
+<?php // $Id$
+require_once($CFG->libdir.'/tablelib.php');
+
+class quiz_report_statistics_question_table extends flexible_table {
+ /**
+ * @var object this question with _stats object.
+ */
+ var $question;
+
+ function quiz_report_statistics_question_table($qid){
+ parent::flexible_table('mod-quiz-report-statistics-question-table'.$qid);
+ }
+
+ /**
+ * Setup the columns and headers and other properties of the table and then
+ * call flexible_table::setup() method.
+ */
+ function setup($reporturl, $question, $hassubqs){
+ $this->question = $question;
+ // Define table columns
+ $columns = array();
+ $headers = array();
+
+ if ($hassubqs){
+ $columns[]= 'subq';
+ $headers[]= '';
+ }
+
+ $columns[]= 'response';
+ $headers[]= get_string('response', 'quiz_statistics');
+
+
+ $columns[]= 'credit';
+ $headers[]= get_string('optiongrade', 'quiz_statistics');
+
+ $columns[]= 'rcount';
+ $headers[]= get_string('count', 'quiz_statistics');
+
+ $columns[]= 'frequency';
+ $headers[]= get_string('frequency', 'quiz_statistics');
+
+ $this->define_columns($columns);
+ $this->define_headers($headers);
+ $this->sortable(false);
+
+ $this->column_class('credit', 'numcol');
+ $this->column_class('rcount', 'numcol');
+ $this->column_class('frequency', 'numcol');
+
+ // Set up the table
+ $this->define_baseurl($reporturl->out());
+
+ $this->collapsible(false);
+
+ $this->set_attribute('class', 'generaltable generalbox boxaligncenter');
+
+ parent::setup();
+ }
+
+ function col_subq($response){
+ return $response->subq;
+ }
+
+ function col_credit($response){
+ if (!is_null($response->credit)){
+ return ($response->credit*100).'%';
+ } else {
+ return '';
+ }
+ }
+
+ function col_frequency($response){
+ if ($this->question->_stats->s){
+ return format_float((($response->rcount / $this->question->_stats->s)*100),2).'%';
+ } else {
+ return '';
+ }
+ }
+
+
+
+}
+?>
$this->collapsible(true);
-/* $this->column_suppress('picture');
- $this->column_suppress('fullname');
- $this->column_suppress('idnumber');
- $this->no_sorting('feedbacktext');
-
- $this->column_class('picture', 'picture');
- $this->column_class('lastname', 'bold');
- $this->column_class('firstname', 'bold');
- $this->column_class('fullname', 'bold');
- $this->column_class('sumgrades', 'bold');*/
-
$this->set_attribute('id', 'questionstatistics');
$this->set_attribute('class', 'generaltable generalbox boxaligncenter');
return quiz_question_action_icons($this->quiz, $this->cmid, $question, $this->baseurl);
}
function col_qtype($question){
- return $question->qtype;
+ return get_string($question->qtype,'quiz');
}
function col_intended_weight($question){
return quiz_report_scale_sumgrades_as_percentage($question->_stats->maxgrade, $this->quiz);
<?php
-$plugin->version = 2008081500; // The (date) version of this module
+$plugin->version = 2008090500; // The (date) version of this module
?>
\ No newline at end of file
return 'calculated';
}
+ function has_wildcards_in_responses($question, $subqid) {
+ return true;
+ }
+
+ function requires_qtypes() {
+ return array('numerical');
+ }
+
function get_question_options(&$question) {
// First get the datasets and default options
global $CFG, $DB;
function restore_session_and_responses(&$question, &$state) {
global $DB;
+ static $subquestions = array();
+ if (!isset($subquestions[$question->id])){
+ if (!$subquestions[$question->id] = $DB->get_records('question_match_sub', array('question' => $question->id), 'id ASC')) {
+ notify('Error: Missing subquestions!');
+ return false;
+ }
+ }
// The serialized format for matching questions is a comma separated
// list of question answer pairs (e.g. 1-1,2-3,3-2), where the ids of
// both refer to the id in the table question_match_sub.
$responses = array_map(create_function('$val',
'return explode("-", $val);'), $responses);
- if (!$questions = $DB->get_records('question_match_sub', array('question' => $question->id), 'id ASC')) {
- notify('Error: Missing subquestions!');
- return false;
- }
+
// Restore the previous responses and place the questions into the state options
$state->responses = array();
$state->options->subquestions = array();
foreach ($responses as $response) {
$state->responses[$response[0]] = $response[1];
- $state->options->subquestions[$response[0]] = $questions[$response[0]];
+ $state->options->subquestions[$response[0]] = clone($subquestions[$question->id][$response[0]]);
}
foreach ($state->options->subquestions as $key => $subquestion) {
return $result;
}
+ function get_possible_responses(&$question) {
+ $answers = array();
+ if (is_array($question->options->subquestions)) {
+ foreach ($question->options->subquestions as $subqid => $answer) {
+ if ($answer->questiontext) {
+ $r = new stdClass;
+ $r->answer = $answer->questiontext . ": " . $answer->answertext;
+ $r->credit = 1;
+ $answers[$subqid] = array($answer->id =>$r);
+ }
+ }
+ }
+ return $answers;
+ }
+
// ULPGC ecastro
function get_actual_response($question, $state) {
$subquestions = &$state->options->subquestions;
}
return $results;
}
-
+
+ function get_actual_response_details($question, $state) {
+ $responses = $this->get_actual_response($question, $state);
+ $teacherresponses = $this->get_possible_responses($question, $state);
+ //only one response
+ $responsedetails =array();
+ foreach ($responses as $tsubqid => $response){
+ $responsedetail = new object();
+ $responsedetail->subqid = $tsubqid;
+ $responsedetail->response = $response;
+ foreach ($teacherresponses[$tsubqid] as $aid => $tresponse){
+ if ($tresponse->answer == $response){
+ $responsedetail->aid = $aid;
+ break;
+ }
+ }
+ if (isset($responsedetail->aid)){
+ $responsedetail->credit = $teacherresponses[$tsubqid][$aid]->credit;
+ } else {
+ $responsedetail->aid = 0;
+ $responsedetail->credit = 0;
+ }
+ $responsedetails[] = $responsedetail;
+ }
+ return $responsedetails;
+ }
+
function response_summary($question, $state, $length=80) {
return shorten_text(implode(', ', $this->get_actual_response($question, $state)), $length);
}
function name() {
return 'multianswer';
}
+
+ function has_wildcards_in_responses($question, $subqid) {
+ global $QTYPES;
+ foreach ($question->options->questions as $subq){
+ if ($subq->id == $subqid){
+ return $QTYPES[$subq->qtype]->has_wildcards_in_responses($subq, $subqid);
+ }
+ }
+ notify('Could not find sub question!');
+ return true;
+ }
+
+ function requires_qtypes() {
+ return array('shortanswer', 'numerical', 'multichoice');
+ }
function get_question_options(&$question) {
global $QTYPES, $DB;
return $responses;
}
+ function get_possible_responses(&$question) {
+ global $QTYPES;
+ $responses = array();
+ foreach($question->options->questions as $key => $wrapped) {
+ if ($wrapped != ''){
+ if ($correct = $QTYPES[$wrapped->qtype]->get_possible_responses($wrapped)) {
+ $responses += $correct;
+ } else {
+ // if there is no correct answer to this subquestion then there
+ // can not be a correct answer to the whole question either, so
+ // we have to return null.
+ return null;
+ }
+ }
+ }
+ return $responses;
+ }
+ function get_actual_response_details($question, $state){
+ global $QTYPES;
+ $details = array();
+ foreach($question->options->questions as $key => $wrapped) {
+ if ($wrapped != ''){
+ $stateforquestion = clone($state);
+ $stateforquestion->responses[''] = $state->responses[$key];
+ $details = array_merge($details, $QTYPES[$wrapped->qtype]->get_actual_response_details($wrapped, $stateforquestion));
+ }
+ }
+ return $details;
+ }
+
function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
global $QTYPES, $CFG, $USER;
function name() {
return 'numerical';
}
+
+ function has_wildcards_in_responses() {
+ return true;
+ }
+
+ function requires_qtypes() {
+ return array('shortanswer');
+ }
function get_question_options(&$question) {
// Get the question answers and their respective tolerances
}
/**
- * The name this question should appear as in the create new question
- * dropdown.
+ * Returns a list of other question types that this one requires in order to
+ * work. For example, the calculated question type is a subclass of the
+ * numerical question type, which is a subclass of the shortanswer question
+ * type; and the randomsamatch question type requires the shortanswer type
+ * to be installed.
*
- * @return mixed the desired string, or false to hide this question type in the menu.
+ * @return array any other question types that this one relies on. An empty
+ * array if none.
*/
- function menu_name() {
+ function requires_qtypes() {
+ return array();
+ }
+
+ /**
+ * @return string the name of this question type in the user's language.
+ * You should not need to override this method, the default behaviour should be fine.
+ */
+ function local_name() {
$name = $this->name();
$menu_name = get_string($name, 'qtype_' . $name);
if ($menu_name[0] == '[') {
return $menu_name;
}
+ /**
+ * The name this question should appear as in the create new question
+ * dropdown. Override this method to return false if you don't want your
+ * question type to be createable, for example if it is an abstract base type,
+ * otherwise, you should not need to override this method.
+ *
+ * @return mixed the desired string, or false to hide this question type in the menu.
+ */
+ function menu_name() {
+ return $this->local_name();
+ }
+
/**
* @return boolean true if this question can only be graded manually.
*/
return false;
}
+ /**
+ * @return boolean true if a table analyzing responses should be shown in
+ * the quiz statistics report. Usually if a question is manually graded
+ * then this analysis table won't be a good idea.
+ */
+ function show_analysis_of_responses() {
+ return !$this->is_manual_graded();
+ }
+
/**
* @return boolean true if this question type can be used by the random question type.
*/
return true;
}
+ /**
+ * @param question record.
+ * @param integer subqid this is the id of the subquestion. Usually the id
+ * of the question record of the question record but this is dependent on
+ * the question type. Not relevant to some question types.
+ * @return whether the teacher supplied responses can include wildcards. Can
+ * more than one answer be equivalent to one teacher supplied response.
+ */
+ function has_wildcards_in_responses($question, $subqid) {
+ return false;
+ }
+
/**
* @return whether the question_answers.answer field needs to have
* restore_decode_content_links_worker called on it.
return null;
}
}
+ /**
+ * The difference between this method an get_all_responses is that this
+ * method is not passed a state object. It is the possible answers to a
+ * question no matter what the state.
+ * This method is not called for random questions.
+ * @return array of possible answers.
+ */
+ function get_possible_responses(&$question) {
+ static $responses = array();
+ if (!isset($responses[$question->id])){
+ $responses[$question->id] = $this->get_all_responses($question, new object());
+ }
+ return array($question->id => $responses[$question->id]->responses);
+ }
/**
* @param object $question
return $responses;
}
+ function get_actual_response_details($question, $state) {
+ $response = array_shift($this->get_actual_response($question, $state));
+ $teacherresponses = $this->get_possible_responses($question, $state);
+ //only one response
+ list($tsubqid, $tresponses) = each($teacherresponses);
+ $responsedetail = new object();
+ $responsedetail->subqid = $tsubqid;
+ $responsedetail->response = $response;
+ if ($aid = $this->check_response($question, $state)){
+ $responsedetail->aid = $aid;
+ } else {
+ foreach ($tresponses as $aid => $tresponse){
+ if ($tresponse->answer == $response){
+ $responsedetail->aid = $aid;
+ break;
+ }
+ }
+ }
+ if (isset($responsedetail->aid)){
+ $responsedetail->credit = $tresponses[$aid]->credit;
+ } else {
+ $responsedetail->aid = 0;
+ $responsedetail->credit = 0;
+ }
+ return array($responsedetail);
+ }
+
// ULPGC ecastro
function get_fractional_grade(&$question, &$state) {
$maxgrade = $question->maxgrade;
return 'randomsamatch';
}
+ function requires_qtypes() {
+ return array('shortanswer');
+ }
+
function is_usable_by_random() {
return false;
}
function restore_session_and_responses(&$question, &$state) {
global $DB;
global $QTYPES;
+ static $wrappedquestions = array();
if (empty($state->responses[''])) {
$question->questiontext = "Insufficient selection options are
available for this question, therefore it is not available in this
// Restore the previous responses
$state->responses = array();
foreach ($responses as $response) {
- $state->responses[$response[0]] = $response[1];
- if (!$wrappedquestion = $DB->get_record('question', array('id' => $response[0]))) {
- notify("Couldn't get question (id=$response[0])!");
- return false;
- }
- if (!$QTYPES[$wrappedquestion->qtype]
- ->get_question_options($wrappedquestion)) {
- notify("Couldn't get question options (id=$response[0])!");
- return false;
- }
-
- // Now we overwrite the $question->options->answers field to only
- // *one* (the first) correct answer. This loop can be deleted to
- // take all answers into account (i.e. put them all into the
- // drop-down menu.
- $foundcorrect = false;
- foreach ($wrappedquestion->options->answers as $answer) {
- if ($foundcorrect || $answer->fraction != 1.0) {
- unset($wrappedquestion->options->answers[$answer->id]);
- } else if (!$foundcorrect) {
- $foundcorrect = true;
+ $wqid = $response[0];
+ $state->responses[$wqid] = $response[1];
+ if (!isset($wrappedquestions[$wqid])){
+ if (!$wrappedquestions[$wqid] = $DB->get_record('question', array('id' => $wqid))) {
+ notify("Couldn't get question (id=$wqid)!");
+ return false;
+ }
+ if (!$QTYPES[$wrappedquestions[$wqid]->qtype]
+ ->get_question_options($wrappedquestions[$wqid])) {
+ notify("Couldn't get question options (id=$response[0])!");
+ return false;
+ }
+
+ // Now we overwrite the $question->options->answers field to only
+ // *one* (the first) correct answer. This loop can be deleted to
+ // take all answers into account (i.e. put them all into the
+ // drop-down menu.
+ $foundcorrect = false;
+ foreach ($wrappedquestions[$wqid]->options->answers as $answer) {
+ if ($foundcorrect || $answer->fraction != 1.0) {
+ unset($wrappedquestions[$wqid]->options->answers[$answer->id]);
+ } else if (!$foundcorrect) {
+ $foundcorrect = true;
+ }
}
}
+ $wrappedquestion = clone($wrappedquestions[$wqid]);
if (!$QTYPES[$wrappedquestion->qtype]
->restore_session_and_responses($wrappedquestion, $state)) {
$result->responses = $answers;
return $result;
}
+ /**
+ * The difference between this method an get_all_responses is that this
+ * method is not passed a state object. It is the possible answers to a
+ * question no matter what the state.
+ * This method is not called for random questions.
+ * @return array of possible answers.
+ */
+ function get_possible_responses(&$question) {
+ global $QTYPES;
+ static $answers = array();
+ if (!isset($answers[$question->id])){
+ if ($question->options->subcats) {
+ // recurse into subcategories
+ $categorylist = question_categorylist($question->category);
+ } else {
+ $categorylist = $question->category;
+ }
+
+ $question->options->subquestions = $this->get_sa_candidates($categorylist);
+ foreach ($question->options->subquestions as $key => $wrappedquestion) {
+ if (!$QTYPES[$wrappedquestion->qtype]
+ ->get_question_options($wrappedquestion)) {
+ return false;
+ }
+
+ // Now we overwrite the $question->options->answers field to only
+ // *one* (the first) correct answer. This loop can be deleted to
+ // take all answers into account (i.e. put them all into the
+ // drop-down menu.
+ $foundcorrect = false;
+ foreach ($wrappedquestion->options->answers as $answer) {
+ if ($foundcorrect || $answer->fraction != 1.0) {
+ unset($wrappedquestion->options->answers[$answer->id]);
+ } else if (!$foundcorrect) {
+ $foundcorrect = true;
+ }
+ }
+ }
+ $answers[$question->id] = array();
+ if (is_array($question->options->subquestions)) {
+ foreach ($question->options->subquestions as $subqid => $answer) {
+ if ($answer->questiontext) {
+ $ans = array_shift($answer->options->answers);
+ $answer->answertext = $ans->answer ;
+ $r = new stdClass;
+ $r->answer = $answer->questiontext . ": " . $answer->answertext;
+ $r->credit = 1;
+ $answers[$question->id][$subqid] = array($ans->id => $r);
+ }
+ }
+ }
+ }
+ return $answers[$question->id];
+ }
+
/**
* @param object $question
* @return mixed either a integer score out of 1 that the average random
return 'shortanswer';
}
+ function has_wildcards_in_responses($question, $subqid) {
+ return true;
+ }
+
function get_question_options(&$question) {
global $DB;
// Get additional information from database