and code refactoring and clean up.
$download = optional_param('download', '', PARAM_ALPHA);
$everything = optional_param('everything', 0, PARAM_BOOL);
$recalculate = optional_param('recalculate', 0, PARAM_BOOL);
- // A qid paramter indicates we should display the detailed analysis of a question.
+ // A qid paramter indicates we should display the detailed analysis of a sub question.
$qid = optional_param('qid', 0, PARAM_INT);
$slot = optional_param('slot', 0, PARAM_INT);
$questionstats = array();
$subquestionstats = array();
}
- $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
// Set up the table, if there is data.
if ($quizstats->s()) {
if ($everything) { // Implies is downloading.
// Overall report, then the analysis of each question.
+ $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
$this->download_quiz_info_table($quizinfo);
if ($quizstats->s()) {
} else if ($qid) {
// Report on an individual sub-question indexed questionid.
- if (!isset($subquestions[$qid])) {
+ if (!isset($subquestionstats[$qid])) {
print_error('questiondoesnotexist', 'question');
}
} else if ($this->table->is_downloading()) {
// Downloading overview report.
+ $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
$this->download_quiz_info_table($quizinfo);
$this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
$this->table->finish_output();
echo $this->output_caching_info($quizstats, $quiz->id, $currentgroup,
$groupstudents, $useallattempts, $reporturl);
echo $this->everything_download_options();
+ $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
echo $this->output_quiz_info_table($quizinfo);
if ($quizstats->s()) {
echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'));
/**
* Display the statistical and introductory information about a question.
* Only called when not downloading.
- * @param object $quiz the quiz settings.
+ * @param object $quiz the quiz settings.
* @param \core_question\statistics\questions\calculated $questionstat the question to report on.
- * @param moodle_url $reporturl the URL to resisplay this report.
- * @param object $quizstats Holds the quiz statistics.
*/
protected function output_individual_question_data($quiz, $questionstat) {
global $OUTPUT;
/**
* Display the response analysis for a question.
- * @param object $question the question to report on.
- * @param moodle_url $reporturl the URL to resisplay this report.
+ * @param object $question the question to report on.
+ * @param int $s
+ * @param moodle_url $reporturl the URL to redisplay this report.
* @param qubaid_condition $qubaids
*/
protected function output_individual_question_response_analysis($question, $s, $reporturl, $qubaids) {
$questiontabletitle = '(' . $question->number . ') ' . $questiontabletitle;
}
if ($this->table->is_downloading() == 'xhtml') {
- $questiontabletitle = get_string('analysisofresponsesfor',
- 'quiz_statistics', $questiontabletitle);
+ $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
}
// Set up the table.
}
}
- $responesstats = new \core_question\statistics\responses\analyser($question);
- $responesstats->load_cached($qubaids);
+ $responesanalyser = new \core_question\statistics\responses\analyser($question);
+ $responseanalysis = $responesanalyser->load_cached($qubaids);
- $qtable->question_setup($reporturl, $question, $s, $responesstats);
+ $qtable->question_setup($reporturl, $question, $s, $responseanalysis);
if ($this->table->is_downloading()) {
$exportclass->output_headers($qtable->headers);
}
-
- foreach ($responesstats->responseclasses as $partid => $partclasses) {
- $rowdata = new stdClass();
- $rowdata->part = $partid;
- foreach ($partclasses as $responseclassid => $responseclass) {
- $rowdata->responseclass = $responseclass->responseclass;
-
- $responsesdata = $responesstats->responses[$partid][$responseclassid];
- if (empty($responsesdata)) {
- if (!array_key_exists('responseclass', $qtable->columns)) {
- $rowdata->response = $responseclass->responseclass;
- } else {
- $rowdata->response = '';
- }
- $rowdata->fraction = $responseclass->fraction;
- $rowdata->count = 0;
- $qtable->add_data_keyed($qtable->format_row($rowdata));
- continue;
- }
-
- foreach ($responsesdata as $response => $data) {
- $rowdata->response = $response;
- $rowdata->fraction = $data->fraction;
- $rowdata->count = $data->count;
- $qtable->add_data_keyed($qtable->format_row($rowdata));
+ foreach ($responseanalysis->get_subpart_ids() as $partid) {
+ $subpart = $responseanalysis->get_subpart($partid);
+ foreach ($subpart->get_response_class_ids() as $responseclassid) {
+ $responseclass = $subpart->get_response_class($responseclassid);
+ $tabledata = $responseclass->data_for_question_response_table($subpart->has_multiple_response_classes(), $partid);
+ foreach ($tabledata as $row) {
+ $qtable->add_data_keyed($qtable->format_row($row));
}
}
}
$groupstudents, count($questions), $qcalc->get_sum_of_mark_variance());
if ($quizstats->s()) {
- $this->calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats);
+ $this->analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats);
}
} else {
$quizstats = $quizcalc->get_cached($qubaids);
return array($quizstats, $questionstats, $subquestionstats);
}
- protected function calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats) {
+ protected function analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats) {
$done = array();
foreach ($questions as $question) {
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_statistics_question_table extends flexible_table {
- /** @var object this question. */
+ /** @var object full question object for this question. */
protected $questiondata;
+ /** @var int no of attempts. */
+ protected $s;
+
/**
* Constructor.
* @param int $qid the id of the particular question whose statistics are being
}
/**
- * @param moodle_url $reporturl
- * @param object $questiondata
- * @param integer $s number of attempts on this question.
- * @param \core_question\statistics\responses\analyser $responesstats
+ * @param moodle_url $reporturl
+ * @param object $questiondata
+ * @param integer $s number of attempts on this question.
+ * @param \core_question\statistics\responses\analysis_for_question $responseanalysis
*/
- public function question_setup($reporturl, $questiondata, $s, \core_question\statistics\responses\analyser $responesstats) {
+ public function question_setup($reporturl, $questiondata, $s, $responseanalysis) {
$this->questiondata = $questiondata;
$this->s = $s;
$columns = array();
$headers = array();
- if ($responesstats->has_subparts()) {
+ if ($responseanalysis->has_subparts()) {
$columns[] = 'part';
$headers[] = get_string('partofquestion', 'quiz_statistics');
}
- if ($responesstats->has_response_classes()) {
+ if ($responseanalysis->has_multiple_response_classes()) {
$columns[] = 'responseclass';
$headers[] = get_string('modelresponse', 'quiz_statistics');
- if ($responesstats->has_actual_responses()) {
+ if ($responseanalysis->has_actual_responses()) {
$columns[] = 'response';
$headers[] = get_string('actualresponse', 'quiz_statistics');
}
/**
* The frequency with which this response was given.
- * @param object $response containst the data to display.
+ * @param object $response contains the data to display.
* @return string contents of this table cell.
*/
protected function col_frequency($response) {
}
/**
- * @return array subpartid => object with fields
- * ->responseclassid matches one of the values returned from
- * quetion_type::get_possible_responses.
- * ->response the actual response the student gave to this part, as a string.
- * ->fraction the credit awarded for this subpart, may be null.
- * returns an empty array if no analysis is possible.
+ * @return question_possible_response[] where keys are subpartid or an empty array if no classification is possible.
*/
public function classify_response() {
return $this->question->classify_response($this->qa->get_last_qt_data());
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class analyser {
- /** @var object the data from the database that defines the question. */
+ /** @var object full question data from db. */
protected $questiondata;
/**
- * @var array This is a multi-dimensional array that stores the results of
- * the analysis.
- *
- * The description of {@link question_type::get_possible_responses()} should
- * help understand this description.
- *
- * $this->responses[$subpartid][$responseclassid][$response] is an
- * object with two fields, ->count and ->fraction.
+ * @var analysis_for_question
*/
- public $responses = array();
+ public $analysis;
/**
- * @var array $this->fractions[$subpartid][$responseclassid] is an object
- * with two fields, ->responseclass and ->fraction.
+ * @var array Two index array first index is unique for each sub question part, the second index is the 'class' that this sub
+ * question part can be classified into. This is the return value from {@link \question_type::get_possible_responses()}
*/
public $responseclasses = array();
/**
* Create a new instance of this class for holding/computing the statistics
* for a particular question.
- * @param object $questiondata the data from the database defining this question.
+ *
+ * @param object $questiondata the full question data from the database defining this question.
*/
public function __construct($questiondata) {
$this->questiondata = $questiondata;
+ $qtypeobj = \question_bank::get_qtype($this->questiondata->qtype);
+ $this->analysis = new analysis_for_question($qtypeobj->get_possible_responses($this->questiondata));
- $this->responseclasses = \question_bank::get_qtype($questiondata->qtype)->get_possible_responses($questiondata);
- foreach ($this->responseclasses as $subpartid => $responseclasses) {
- foreach ($responseclasses as $responseclassid => $notused) {
- $this->responses[$subpartid][$responseclassid] = array();
- }
- }
}
/**
}
/**
- * @return bool whether this analysis has (a subpart with) more than one
- * response class.
+ * @return bool whether this analysis has (a subpart with) more than one response class.
*/
public function has_response_classes() {
foreach ($this->responseclasses as $partclasses) {
* Analyse all the response data for for all the specified attempts at
* this question.
* @param \qubaid_condition $qubaids which attempts to consider.
+ * @return analysis_for_question
*/
public function calculate($qubaids) {
// Load data.
// Analyse it.
foreach ($questionattempts as $qa) {
- $this->add_data_from_one_attempt($qa);
+ $responseparts = $qa->classify_response();
+ $this->analysis->count_response_parts($responseparts);
}
- $this->store_cached($qubaids);
-
+ $this->analysis->cache($qubaids, $this->questiondata->id);
+ return $this->analysis;
}
- /**
- * Analyse the data from one question attempt.
- * @param \question_attempt $qa the data to analyse.
- */
- protected function add_data_from_one_attempt(\question_attempt $qa) {
- $blankresponse = \question_classified_response::no_response();
-
- $partresponses = $qa->classify_response();
- foreach ($partresponses as $subpartid => $partresponse) {
- if (!isset($this->responses[$subpartid][$partresponse->responseclassid]
- [$partresponse->response])) {
- $resp = new \stdClass();
- $resp->count = 0;
- if (!is_null($partresponse->fraction)) {
- $resp->fraction = $partresponse->fraction;
- } else {
- $resp->fraction = $this->responseclasses[$subpartid]
- [$partresponse->responseclassid]->fraction;
- }
+ /** @var integer Time after which responses are automatically reanalysed. */
+ const TIME_TO_CACHE = 900; // 15 minutes.
- $this->responses[$subpartid][$partresponse->responseclassid]
- [$partresponse->response] = $resp;
- }
-
- $this->responses[$subpartid][$partresponse->responseclassid]
- [$partresponse->response]->count += 1;
- }
- }
/**
- * Store the computed response analysis in the question_response_analysis table.
- * @param \qubaid_condition $qubaids
- * data corresponding to.
- * @return bool true if cached data was found in the database and loaded, otherwise false, to mean no data was loaded.
+ * Retrieve the computed response analysis from the question_response_analysis table.
+ *
+ * @param \qubaid_condition $qubaids which attempts to get cached response analysis for.
+ * @return analysis_for_question|boolean analysis or false if no cached analysis found.
*/
public function load_cached($qubaids) {
global $DB;
- $rows = $DB->get_records('question_response_analysis',
- array('hashcode' => $qubaids->get_hash_code(), 'questionid' => $this->questiondata->id));
+ $timemodified = time() - self::TIME_TO_CACHE;
+ $rows = $DB->get_records_select('question_response_analysis', 'hashcode = ? AND questionid = ? AND timemodified > ? ',
+ array($qubaids->get_hash_code(), $this->questiondata->id, $timemodified));
if (!$rows) {
return false;
}
foreach ($rows as $row) {
- $this->responses[$row->subqid][$row->aid][$row->response] = new \stdClass();
- $this->responses[$row->subqid][$row->aid][$row->response]->count = $row->rcount;
- $this->responses[$row->subqid][$row->aid][$row->response]->fraction = $row->credit;
+ $class = $this->analysis->get_subpart($row->subqid)->get_response_class($row->aid);
+ $class->add_response_and_count($row->response, $row->credit, $row->rcount);
}
- return true;
+ return $this->analysis;
}
+
/**
- * Store the computed response analysis in the question_response_analysis table.
- * @param \qubaid_condition $qubaids
+ * Find time of non-expired analysis in the database.
+ *
+ * @param $qubaids \qubaid_condition
+ * @return integer|boolean Time of cached record that matches this qubaid_condition or false if none found.
*/
- public function store_cached($qubaids) {
+ public function get_last_analysed_time($qubaids) {
global $DB;
- $cachetime = time();
- foreach ($this->responses as $subpartid => $partdata) {
- foreach ($partdata as $responseclassid => $classdata) {
- foreach ($classdata as $response => $data) {
- $row = new \stdClass();
- $row->hashcode = $qubaids->get_hash_code();
- $row->questionid = $this->questiondata->id;
- $row->subqid = $subpartid;
- if ($responseclassid === '') {
- $row->aid = null;
- } else {
- $row->aid = $responseclassid;
- }
- $row->response = $response;
- $row->rcount = $data->count;
- $row->credit = $data->fraction;
- $row->timemodified = $cachetime;
- $DB->insert_record('question_response_analysis', $row, false);
- }
- }
- }
+ $timemodified = time() - self::TIME_TO_CACHE;
+ return $DB->get_field_select('question_response_analysis', 'timemodified', 'hashcode = ? AND timemodified > ? '.
+ 'ORDER BY timemodified DESC LIMIT 1',
+ array($qubaids->get_hash_code(), $timemodified));
}
}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * ${filedescription}
+ *
+ * @package ${package}_{subpackage}
+ * @copyright 2013 The Open University
+ * @author James Pratt me@jamiep.org
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_question\statistics\responses;
+
+
+class analysis_for_actual_response {
+ /**
+ * @var int count of this response
+ */
+ protected $count;
+
+ /**
+ * @var float grade for this response, normally between 0 and 1.
+ */
+ protected $fraction;
+
+ /**
+ * @var string the response as it will be displayed in report.
+ */
+ protected $response;
+
+ /**
+ * @param string $response
+ * @param float $fraction
+ * @param int $count defaults to zero, this param used when loading from db.
+ */
+ public function __construct($response, $fraction, $count = 0) {
+ $this->response = $response;
+ $this->fraction = $fraction;
+ $this->count = $count;
+ }
+
+ /**
+ * Used to count the occurrences of response sub parts.
+ */
+ public function increment_count() {
+ $this->count++;
+ }
+
+
+ /**
+ * @param \qubaid_condition $qubaids
+ * @param int $questionid the question id
+ * @param string $subpartid
+ * @param string $responseclassid
+ */
+ public function cache($qubaids, $questionid, $subpartid, $responseclassid) {
+ global $DB;
+ $row = new \stdClass();
+ $row->hashcode = $qubaids->get_hash_code();
+ $row->questionid = $questionid;
+ $row->subqid = $subpartid;
+ if ($responseclassid === '') {
+ $row->aid = null;
+ } else {
+ $row->aid = $responseclassid;
+ }
+ $row->response = $this->response;
+ $row->rcount = $this->count;
+ $row->credit = $this->fraction;
+ $row->timemodified = time();
+ $DB->insert_record('question_response_analysis', $row, false);
+ }
+
+ public function response_matches($response) {
+ return $response == $this->response;
+ }
+
+ /**
+ * @param string $partid
+ * @param string $modelresponse
+ * @return object
+ */
+ public function data_for_question_response_table($partid, $modelresponse) {
+ $rowdata = new \stdClass();
+ $rowdata->part = $partid;
+ $rowdata->responseclass = $modelresponse;
+ $rowdata->response = $this->response;
+ $rowdata->fraction = $this->fraction;
+ $rowdata->count = $this->count;
+ return $rowdata;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * ${filedescription}
+ *
+ * @package ${package}_{subpackage}
+ * @copyright 2013 The Open University
+ * @author James Pratt me@jamiep.org
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_question\statistics\responses;
+
+
+
+/**
+ * Represents an actual part of the response that has been classified in a class of responses for this sub part of the question.
+ *
+ * A question and it's response is represented as having one or more sub parts where the response to each sub-part might fall
+ * into one of one or more classes.
+ *
+ * No response is one possible class of response to a question.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class analysis_for_class {
+
+ /**
+ * @var string
+ */
+ protected $responseclassid;
+
+ /**
+ * @var string
+ */
+ protected $modelresponse;
+
+ /** @var string the (partial) credit awarded for this responses. */
+ protected $fraction;
+
+ /**
+ *
+ * @var analysis_for_actual_response[] key is the actual response represented as a string as it will be displayed in report.
+ */
+ protected $actualresponses = array();
+
+ /**
+ * Constructor, just an easy way to set the fields.
+ * @param \question_possible_response $possibleresponse
+ * @param string $responseclassid
+ */
+ public function __construct($possibleresponse, $responseclassid) {
+ $this->modelresponse = $possibleresponse->responseclass;
+ $this->fraction = $possibleresponse->fraction;
+ $this->responseclassid = $responseclassid;
+ }
+
+ /**
+ * @param string $actualresponse
+ * @param float|null $fraction
+ */
+ public function count_response($actualresponse, $fraction) {
+ if (!isset($this->actualresponses[$actualresponse])) {
+ if ($fraction === null) {
+ $fraction = $this->fraction;
+ }
+ $this->actualresponses[$actualresponse] = new analysis_for_actual_response($actualresponse, $fraction);
+ }
+ $this->actualresponses[$actualresponse]->increment_count();
+ }
+
+ /**
+ * @param \qubaid_condition $qubaids
+ * @param int $questionid the question id
+ * @param string $subpartid
+ */
+ public function cache($qubaids, $questionid, $subpartid) {
+ foreach ($this->actualresponses as $response => $actualresponse) {
+ $actualresponse->cache($qubaids, $questionid, $subpartid, $this->responseclassid, $response);
+ }
+ }
+
+ public function add_response_and_count($response, $fraction, $count) {
+ $this->actualresponses[$response] = new analysis_for_actual_response($response, $fraction, $count);
+ }
+
+ /**
+ * @return bool whether this analysis has a response class with more than one
+ * different actual response, or if the actual response is different from
+ * the model response.
+ */
+ public function has_actual_responses() {
+ if (count($this->actualresponses) > 1) {
+ return true;
+ } else if (count($this->actualresponses) == 1) {
+ $onlyactualresponse = reset($this->actualresponses);
+ return !$onlyactualresponse->response_matches($this->modelresponse);
+ }
+ return false;
+ }
+
+ /**
+ * @return object[]
+ */
+ public function data_for_question_response_table($responseclasscolumn, $partid) {
+ $return = array();
+ if (empty($this->actualresponses)) {
+ $rowdata = new \stdClass();
+ $rowdata->part = $partid;
+ $rowdata->responseclass = $this->modelresponse;
+ if (!$responseclasscolumn) {
+ $rowdata->response = $this->modelresponse;
+ } else {
+ $rowdata->response = '';
+ }
+ $rowdata->fraction = $this->fraction;
+ $rowdata->count = 0;
+ $return[] = $rowdata;
+ } else {
+ foreach ($this->actualresponses as $actualresponse) {
+ $return[] = $actualresponse->data_for_question_response_table($partid, $this->modelresponse);
+ }
+ }
+ return $return;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the code to analyse all the responses to a particular
+ * question.
+ *
+ * @package core
+ * @subpackage questionbank
+ * @copyright 2013 Open University
+ * @author Jamie Pratt <me@jamiep.org>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_question\statistics\responses;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Analysis for possible responses for parts of a question. It is up to a question type designer to decide on how many parts their
+ * question has. A sub part might represent a sub question embedded in the question for example in a matching question there are
+ * several sub parts. A numeric question with a unit might be divided into two sub parts for the purposes of response analysis
+ * or the question type designer might decide to treat the answer, both the numeric and unit part,
+ * as a whole for the purposes of response analysis.
+ *
+ * Responses can be further divided into 'classes' in which they are classified. One or more of these 'classes' are contained in
+ * the responses
+ *
+ * @copyright 2013 Open University
+ * @author Jamie Pratt <me@jamiep.org>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class analysis_for_question {
+
+ /**
+ * Takes either a two index array as a parameter with keys subpartid and classid and values possible_response.
+ * Or takes an array of {@link responses_for_classes} objects.
+ *
+ * @param $subparts[string]array[]\question_possible_response $array
+ */
+ public function __construct(array $subparts = null) {
+ if (!is_null($subparts)) {
+ foreach ($subparts as $subpartid => $classes) {
+ $this->subparts[$subpartid] = new analysis_for_subpart($classes);
+ }
+ }
+ }
+
+ /**
+ * @var analysis_for_subpart[]
+ */
+ protected $subparts;
+
+ /**
+ * Unique ids for sub parts.
+ *
+ * @return string[]
+ */
+ public function get_subpart_ids() {
+ return array_keys($this->subparts);
+ }
+
+ /**
+ * @param string $subpartid id for sub part.
+ * @return analysis_for_subpart
+ */
+ public function get_subpart($subpartid) {
+ return $this->subparts[$subpartid];
+ }
+
+ /**
+ * Used to work out what kind of table is needed to display stats.
+ *
+ * @return bool whether this question has (a subpart with) more than one response class.
+ */
+ public function has_multiple_response_classes() {
+ foreach ($this->subparts as $subpart) {
+ if ($subpart->has_multiple_response_classes()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Used to work out what kind of table is needed to display stats.
+ *
+ * @return bool whether this analysis has more than one subpart.
+ */
+ public function has_subparts() {
+ return count($this->subparts) > 1;
+ }
+
+ /**
+ * Takes an array of {@link \question_classified_response} and adds counts of the responses to the sub parts and classes.
+ *
+ * @var \question_classified_response[] $responseparts keys are sub-part id.
+ */
+ public function count_response_parts($responseparts) {
+ foreach ($responseparts as $subpartid => $responsepart) {
+ $this->get_subpart($subpartid)->count_response($responsepart);
+ }
+ }
+
+ /**
+ * @param \qubaid_condition $qubaids
+ * @param int $questionid the question id
+ */
+ public function cache($qubaids, $questionid) {
+ foreach ($this->subparts as $subpartid => $subpart) {
+ $subpart->cache($qubaids, $questionid, $subpartid);
+ }
+ }
+
+ /**
+ * @return bool whether this analysis has a response class with more than one
+ * different actual response, or if the actual response is different from
+ * the model response.
+ */
+ public function has_actual_responses() {
+ foreach ($this->subparts as $subpartid => $subpart) {
+ if ($subpart->has_actual_responses()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ *
+ * 'Classes' to classify the sub parts of a question response into.
+ *
+ * @package core
+ * @subpackage questionbank
+ * @copyright 2013 The Open University
+ * @author James Pratt me@jamiep.org
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_question\statistics\responses;
+
+
+class analysis_for_subpart {
+
+ /**
+ * Takes an array of possible_responses - ({@link \question_possible_response} objects).
+ * Or takes an array of {@link \question_possible_response} objects.
+ *
+ * @param \question_possible_response[] $responseclasses
+ */
+ public function __construct(array $responseclasses = null) {
+ if (is_array($responseclasses)) {
+ foreach ($responseclasses as $responseclassid => $reponseclass) {
+ $this->responseclasses[$responseclassid] = new analysis_for_class($reponseclass, $responseclassid);
+ }
+ }
+ }
+
+ /**
+ *
+ * @var analysis_for_class[]
+ */
+ protected $responseclasses;
+
+ /**
+ * Unique ids for response classes.
+ *
+ * @return string[]
+ */
+ public function get_response_class_ids() {
+ return array_keys($this->responseclasses);
+ }
+
+ /**
+ * @param string $classid id for response class.
+ * @return analysis_for_class
+ */
+ public function get_response_class($classid) {
+ return $this->responseclasses[$classid];
+ }
+
+ public function has_multiple_response_classes() {
+ return count($this->responseclasses) > 1;
+ }
+
+ /**
+ * @param \question_classified_response $subpart
+ */
+ public function count_response($subpart) {
+ $this->responseclasses[$subpart->responseclassid]->count_response($subpart->response, $subpart->fraction);
+ }
+
+ /**
+ * @param \qubaid_condition $qubaids
+ * @param int $questionid the question id
+ * @param string $subpartid
+ */
+ public function cache($qubaids, $questionid, $subpartid) {
+ foreach ($this->responseclasses as $responseclassid => $responseclass) {
+ $responseclass->cache($qubaids, $questionid, $subpartid, $responseclassid);
+ }
+ }
+
+ /**
+ * @return bool whether this analysis has a response class with more than one
+ * different actual response, or if the actual response is different from
+ * the model response.
+ */
+ public function has_actual_responses() {
+ foreach ($this->responseclasses as $responseclass) {
+ if ($responseclass->has_actual_responses()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
}
/**
- * @return array subpartid => object with fields
- * ->responseclassid matches one of the values returned from quetion_type::get_possible_responses.
- * ->response the actual response the student gave to this part, as a string.
- * ->fraction the credit awarded for this subpart, may be null.
- * returns an empty array if no analysis is possible.
+ * Break down a student response by sub part and classification.
+ * See also {@link question_type::get_possible_responses()}
+ * Used for response analysis.
+ *
+ * @return question_possible_response[] where keys are subpartid.
*/
public function classify_response() {
return $this->behaviour->classify_response();