1
0
mirror of https://github.com/moodle/moodle.git synced 2025-05-12 11:15:49 +02:00

Merge branch 'MDL-41751-master' of git://github.com/jamiepratt/moodle

This commit is contained in:
Sam Hemelryk 2013-10-02 11:36:16 +13:00
commit 6579a7b791
9 changed files with 571 additions and 146 deletions

@ -58,7 +58,7 @@ class quiz_statistics_report extends quiz_default_report {
$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);
@ -135,7 +135,6 @@ class quiz_statistics_report extends quiz_default_report {
$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()) {
@ -166,6 +165,7 @@ class quiz_statistics_report extends quiz_default_report {
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()) {
@ -209,7 +209,7 @@ class quiz_statistics_report extends quiz_default_report {
} else if ($qid) {
// Report on an individual sub-question indexed questionid.
if (!isset($subquestions[$qid])) {
if (!isset($subquestionstats[$qid])) {
print_error('questiondoesnotexist', 'question');
}
@ -224,6 +224,7 @@ class quiz_statistics_report extends quiz_default_report {
} 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();
@ -234,6 +235,7 @@ class quiz_statistics_report extends quiz_default_report {
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'));
@ -248,10 +250,8 @@ class quiz_statistics_report extends quiz_default_report {
/**
* 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;
@ -329,8 +329,9 @@ class quiz_statistics_report extends quiz_default_report {
/**
* 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) {
@ -354,8 +355,7 @@ class quiz_statistics_report extends quiz_default_report {
$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.
@ -366,38 +366,20 @@ class quiz_statistics_report extends quiz_default_report {
}
}
$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));
}
}
}
@ -531,7 +513,7 @@ class quiz_statistics_report extends quiz_default_report {
$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);
@ -541,7 +523,7 @@ class quiz_statistics_report extends quiz_default_report {
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) {

@ -38,9 +38,12 @@ require_once($CFG->libdir . '/tablelib.php');
* @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
@ -51,12 +54,12 @@ class quiz_statistics_question_table extends flexible_table {
}
/**
* @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;
@ -68,16 +71,16 @@ class quiz_statistics_question_table extends flexible_table {
$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');
}
@ -129,7 +132,7 @@ class quiz_statistics_question_table extends flexible_table {
/**
* 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) {

@ -301,12 +301,7 @@ abstract class question_behaviour {
}
/**
* @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());

@ -37,41 +37,31 @@ defined('MOODLE_INTERNAL') || die();
* @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();
}
}
}
/**
@ -82,8 +72,7 @@ class analyser {
}
/**
* @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) {
@ -119,6 +108,7 @@ class analyser {
* 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.
@ -127,91 +117,53 @@ class analyser {
// 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();
/** @var integer Time after which responses are automatically reanalysed. */
const TIME_TO_CACHE = 900; // 15 minutes.
$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;
}
$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));
}
}

@ -0,0 +1,106 @@
<?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;
}
}

@ -0,0 +1,141 @@
<?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;
}
}

@ -0,0 +1,141 @@
<?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;
}
}

@ -0,0 +1,105 @@
<?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;
}
}

@ -1242,11 +1242,11 @@ class question_attempt {
}
/**
* @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();