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

This commit is contained in:
Eloy Lafuente (stronk7)
2013-10-09 01:29:17 +02:00
13 changed files with 309 additions and 172 deletions
+2 -3
View File
@@ -84,8 +84,7 @@ abstract class quiz_attempts_report extends quiz_default_report {
$this->qmsubselect = quiz_report_qm_filter_select($quiz);
$this->form = new $formclass($this->get_base_url(),
array('qmsubselect' => $this->qmsubselect, 'quiz' => $quiz,
'currentgroup' => $currentgroup, 'context' => $this->context));
array('quiz' => $quiz, 'currentgroup' => $currentgroup, 'context' => $this->context));
return array($currentgroup, $students, $groupstudents, $allowed);
}
@@ -103,7 +102,7 @@ abstract class quiz_attempts_report extends quiz_default_report {
* Get information about which students to show in the report.
* @param object $cm the coures module.
* @param object $course the course settings.
* @return an array with four elements:
* @return array with four elements:
* 0 => integer the current group id (0 for none).
* 1 => array ids of all the students in this course.
* 2 => array ids of all the students in the current group.
+39 -27
View File
@@ -137,52 +137,64 @@ function quiz_report_can_filter_only_graded($quiz) {
}
/**
* Given the quiz grading method return sub select sql to find the id of the
* one attempt that will be graded for each user. Or return
* empty string if all attempts contribute to final grade.
* This is a wrapper for {@link quiz_report_grade_method_sql} that takes the whole quiz object instead of just the grading method
* as a param. See definition for {@link quiz_report_grade_method_sql} below.
*
* @param object $quiz
* @param string $quizattemptsalias sql alias for 'quiz_attempts' table
* @return string sql to test if this is an attempt that will contribute towards the grade of the user
*/
function quiz_report_qm_filter_select($quiz, $quizattemptsalias = 'quiza') {
if ($quiz->attempts == 1) {
// This quiz only allows one attempt.
return '';
}
return quiz_report_grade_method_sql($quiz->grademethod, $quizattemptsalias);
}
switch ($quiz->grademethod) {
/**
* Given a quiz grading method return sql to test if this is an
* attempt that will be contribute towards the grade of the user. Or return an
* empty string if the grading method is QUIZ_GRADEAVERAGE and thus all attempts
* contribute to final grade.
*
* @param string $grademethod quiz grading method.
* @param string $quizattemptsalias sql alias for 'quiz_attempts' table
* @return string sql to test if this is an attempt that will contribute towards the graded of the user
*/
function quiz_report_grade_method_sql($grademethod, $quizattemptsalias = 'quiza') {
switch ($grademethod) {
case QUIZ_GRADEHIGHEST :
return "$quizattemptsalias.id = (
SELECT MIN(qa2.id)
FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
qa2.userid = $quizattemptsalias.userid AND
COALESCE(qa2.sumgrades, 0) = (
SELECT MAX(COALESCE(qa3.sumgrades, 0))
FROM {quiz_attempts} qa3
WHERE qa3.quiz = $quizattemptsalias.quiz AND
qa3.userid = $quizattemptsalias.userid
)
)";
SELECT MIN(qa2.id)
FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
qa2.userid = $quizattemptsalias.userid AND
COALESCE(qa2.sumgrades, 0) = (
SELECT MAX(COALESCE(qa3.sumgrades, 0))
FROM {quiz_attempts} qa3
WHERE qa3.quiz = $quizattemptsalias.quiz AND
qa3.userid = $quizattemptsalias.userid
)
)";
case QUIZ_GRADEAVERAGE :
return '';
case QUIZ_ATTEMPTFIRST :
return "$quizattemptsalias.id = (
SELECT MIN(qa2.id)
FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
qa2.userid = $quizattemptsalias.userid)";
return "$quizattemptsalias.attempt = 1";
case QUIZ_ATTEMPTLAST :
return "$quizattemptsalias.id = (
SELECT MAX(qa2.id)
FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
qa2.userid = $quizattemptsalias.userid)";
SELECT MAX(qa2.id)
FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
qa2.userid = $quizattemptsalias.userid)";
}
}
/**
* Get the nuber of students whose score was in a particular band for this quiz.
* Get the number of students whose score was in a particular band for this quiz.
* @param number $bandwidth the width of each band.
* @param int $bands the number of bands
* @param int $quizid the quiz id.
@@ -225,10 +237,10 @@ ORDER BY
$data = $DB->get_records_sql_menu($sql, $params);
// We need to create array elements with values 0 at indexes where there is no element.
$data = $data + array_fill(0, $bands + 1, 0);
$data = $data + array_fill(0, $bands + 1, 0);
ksort($data);
// Place the maximum (prefect grade) into the last band i.e. make last
// Place the maximum (perfect grade) into the last band i.e. make last
// band for example 9 <= g <=10 (where 10 is the perfect grade) rather than
// just 9 <= g <10.
$data[$bands - 1] += $data[$bands];
@@ -28,16 +28,24 @@
*/
class quiz_statistics_calculated {
public function __construct($allattempts = null) {
if ($allattempts !== null) {
$this->allattempts = $allattempts;
/**
* @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student,
* the default null value is used when constructing an instance whose values will be
* populated from a db record.
*/
public function __construct($whichattempts = null) {
if ($whichattempts !== null) {
$this->whichattempts = $whichattempts;
}
}
/**
* @var bool whether we are calculating calculate stats from all attempts.
* @var int which attempts we are calculating calculate stats from.
*/
public $allattempts;
public $whichattempts;
/* Following stats all described here : http://docs.moodle.org/dev/Quiz_statistics_calculations#Test_statistics */
@@ -45,10 +53,18 @@ class quiz_statistics_calculated {
public $allattemptscount = 0;
public $lastattemptscount = 0;
public $highestattemptscount = 0;
public $firstattemptsavg;
public $allattemptsavg;
public $lastattemptsavg;
public $highestattemptsavg;
public $median;
public $standarddeviation;
@@ -68,20 +84,34 @@ class quiz_statistics_calculated {
*/
public $timemodified;
/**
* Count of attempts selected by $this->whichattempts
*
* @return int
*/
public function s() {
if ($this->allattempts) {
return $this->allattemptscount;
} else {
return $this->firstattemptscount;
}
return $this->get_field('count');
}
/**
* Average grade for the attempts selected by $this->whichattempts
*
* @return float
*/
public function avg() {
if ($this->allattempts) {
return $this->allattemptsavg;
} else {
return $this->firstattemptsavg;
}
return $this->get_field('avg');
}
/**
* Get the right field name to fetch a stat for these attempts that is calculated for more than one $whichattempts (count or
* avg).
*
* @param string $field name of field
* @return int|float
*/
protected function get_field($field) {
$fieldname = quiz_statistics_calculator::using_attempts_string_id($this->whichattempts).$field;
return $this->{$fieldname};
}
/**
@@ -97,6 +127,8 @@ class quiz_statistics_calculated {
'allattemptscount' => 'number',
'firstattemptsavg' => 'summarks_as_percentage',
'allattemptsavg' => 'summarks_as_percentage',
'lastattemptsavg' => 'summarks_as_percentage',
'highestattemptsavg' => 'summarks_as_percentage',
'median' => 'summarks_as_percentage',
'standarddeviation' => 'summarks_as_percentage',
'skewness' => 'number_format',
@@ -149,27 +181,18 @@ class quiz_statistics_calculated {
$formattedvalue = $value;
}
$quizinfo[get_string($property, 'quiz_statistics', $this->using_attempts_string())] = $formattedvalue;
$quizinfo[get_string($property, 'quiz_statistics',
quiz_statistics_calculator::using_attempts_lang_string($this->whichattempts))] = $formattedvalue;
}
return $quizinfo;
}
/**
* @return string the appropriate lang string to describe this option.
*/
protected function using_attempts_string() {
if ($this->allattempts) {
return get_string('allattempts', 'quiz_statistics');
} else {
return get_string('firstattempts', 'quiz_statistics');
}
}
/**
* @var array of names of properties of this class that are cached in db record.
*/
protected $fieldsindb = array('allattempts', 'firstattemptscount', 'allattemptscount', 'firstattemptsavg', 'allattemptsavg',
protected $fieldsindb = array('whichattempts', 'firstattemptscount', 'allattemptscount', 'firstattemptsavg', 'allattemptsavg',
'lastattemptscount', 'highestattemptscount', 'lastattemptsavg', 'highestattemptsavg',
'median', 'standarddeviation', 'skewness',
'kurtosis', 'cic', 'errorratio', 'standarderror');
@@ -32,16 +32,26 @@ class quiz_statistics_calculator {
* Compute the quiz statistics.
*
* @param int $quizid the quiz id.
* @param int $currentgroup the current group. 0 for none.
* @param bool $useallattempts use all attempts, or just first attempts.
* @param int $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student.
* @param array $groupstudents students in this group.
* @param int $p number of positions (slots).
* @param float $sumofmarkvariance sum of mark variance, calculated as part of question statistics
* @return quiz_statistics_calculated $quizstats The statistics for overall attempt scores.
*/
public function calculate($quizid, $currentgroup, $useallattempts, $groupstudents, $p, $sumofmarkvariance) {
public function calculate($quizid, $whichattempts, $groupstudents, $p, $sumofmarkvariance) {
$quizstats = $this->attempt_counts_and_averages($quizid, $currentgroup, $useallattempts, $groupstudents);
$quizstats = new quiz_statistics_calculated($whichattempts);
$countsandaverages = $this->attempt_counts_and_averages($quizid, $groupstudents);
foreach ($countsandaverages as $propertyname => $value) {
$quizstats->{$propertyname} = $value;
}
$s = $quizstats->s();
@@ -51,7 +61,7 @@ class quiz_statistics_calculator {
// Recalculate sql again this time possibly including test for first attempt.
list($fromqa, $whereqa, $qaparams) =
quiz_statistics_attempts_sql($quizid, $currentgroup, $groupstudents, $useallattempts);
quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts);
$quizstats->median = $this->median($s, $fromqa, $whereqa, $qaparams);
@@ -80,7 +90,7 @@ class quiz_statistics_calculator {
}
if ($p > 1) {
$quizstats->cic = (100 * $p / ($p -1)) * (1 - ($sumofmarkvariance / $k2));
$quizstats->cic = (100 * $p / ($p - 1)) * (1 - ($sumofmarkvariance / $k2));
$quizstats->errorratio = 100 * sqrt(1 - ($quizstats->cic / 100));
$quizstats->standarderror = $quizstats->errorratio *
$quizstats->standarddeviation / 100;
@@ -90,7 +100,7 @@ class quiz_statistics_calculator {
}
}
$quizstats->cache(quiz_statistics_qubaids_condition($quizid, $currentgroup, $groupstudents, $useallattempts));
$quizstats->cache(quiz_statistics_qubaids_condition($quizid, $groupstudents, $whichattempts));
return $quizstats;
}
@@ -129,61 +139,88 @@ class quiz_statistics_calculator {
array($qubaids->get_hash_code(), $timemodified));
}
/**
* Given a particular quiz grading method return a lang string describing which attempts contribute to grade.
*
* Note internally we use the grading method constants to represent which attempts we are calculating statistics for, each
* grading method corresponds to different attempts for each user.
*
* @param int $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student.
* @return string the appropriate lang string to describe this option.
*/
public static function using_attempts_lang_string($whichattempts) {
return get_string(static::using_attempts_string_id($whichattempts), 'quiz_statistics');
}
/**
* Given a particular quiz grading method return a string id for use as a field name prefix in mdl_quiz_statistics or to
* fetch the appropriate language string describing which attempts contribute to grade.
*
* Note internally we use the grading method constants to represent which attempts we are calculating statistics for, each
* grading method corresponds to different attempts for each user.
*
* @param int $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student.
* @return string the string id for this option.
*/
public static function using_attempts_string_id($whichattempts) {
switch ($whichattempts) {
case QUIZ_ATTEMPTFIRST :
return 'firstattempts';
case QUIZ_GRADEHIGHEST :
return 'highestattempts';
case QUIZ_ATTEMPTLAST :
return 'lastattempts';
case QUIZ_GRADEAVERAGE :
return 'allattempts';
}
}
/**
* Calculating count and mean of marks for first and ALL attempts by students.
*
* See : http://docs.moodle.org/dev/Quiz_item_analysis_calculations_in_practise
* #Calculating_MEAN_of_grades_for_all_attempts_by_students
* @param int $quizid
* @param int $currentgroup
* @param bool $useallattempts
* @param array $groupstudents
* @return quiz_statistics_calculated containing calculated counts, totals and averages.
* @return stdClass with properties with count and avg with prefixes firstattempts, highestattempts, etc.
*/
protected function attempt_counts_and_averages($quizid, $currentgroup, $useallattempts, $groupstudents) {
protected function attempt_counts_and_averages($quizid, $groupstudents) {
global $DB;
$quizstats = new quiz_statistics_calculated($useallattempts);
list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents);
list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $currentgroup, $groupstudents, true);
$selects = array();
foreach (array_keys(quiz_get_grading_options()) as $which) {
$fieldprefix = static::using_attempts_string_id($which);
$condition = quiz_report_grade_method_sql($which);
if ($condition == '') {
$case = '1';
} else {
$case = "CASE WHEN ($condition) THEN 1 ELSE 0 END";
}
$selects[] = "
SUM($case) AS {$fieldprefix}count,
SUM(sumgrades * $case) AS {$fieldprefix}total";
}
$select = join(',', $selects);
$attempttotals = $DB->get_records_sql("
SELECT
CASE WHEN attempt = 1 THEN 1 ELSE 0 END AS isfirst,
COUNT(1) AS countrecs,
SUM(sumgrades) AS total
$attempttotals = $DB->get_record_sql("
SELECT {$select}
FROM $fromqa
WHERE $whereqa
GROUP BY CASE WHEN attempt = 1 THEN 1 ELSE 0 END", $qaparams);
WHERE $whereqa", $qaparams);
// Above query that returns sums and counts for first attempt and other non first attempts.
// We want to work out stats for first attempt or ALL attempts.
if (isset($attempttotals[1])) {
$quizstats->firstattemptscount = $attempttotals[1]->countrecs;
$firstattemptstotal = $attempttotals[1]->total;
} else {
$quizstats->firstattemptscount = 0;
$firstattemptstotal = 0;
foreach (array_keys(quiz_get_grading_options()) as $which) {
$fieldprefix = static::using_attempts_string_id($which);
$attempttotals->{$fieldprefix.'avg'} = $attempttotals->{$fieldprefix.'total'} / $attempttotals->{$fieldprefix.'count'};
unset($attempttotals->{$fieldprefix.'total'});
}
if (isset($attempttotals[0])) {
$quizstats->allattemptscount = $quizstats->firstattemptscount + $attempttotals[0]->countrecs;
$allattemptstotal = $firstattemptstotal + $attempttotals[0]->total;
} else {
$quizstats->allattemptscount = $quizstats->firstattemptscount;
$allattemptstotal = $firstattemptstotal;
}
if ($quizstats->allattemptscount !== 0) {
$quizstats->allattemptsavg = $allattemptstotal / $quizstats->allattemptscount;
}
if ($quizstats->firstattemptscount !== 0) {
$quizstats->firstattemptsavg = $firstattemptstotal / $quizstats->firstattemptscount;
}
return $quizstats;
return $attempttotals;
}
/**
+5 -1
View File
@@ -8,11 +8,15 @@
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="hashcode" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false" COMMENT="sha1 hash of serialized qubaids_condition class. Unique for every combination of class name and property."/>
<FIELD NAME="allattempts" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="bool used to indicate whether these stats are for all attempts or just for the first."/>
<FIELD NAME="whichattempts" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="bool used to indicate whether these stats are for all attempts or just for the first."/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="firstattemptscount" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="highestattemptscount" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="lastattemptscount" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="allattemptscount" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="firstattemptsavg" TYPE="number" LENGTH="15" NOTNULL="false" SEQUENCE="false" DECIMALS="5"/>
<FIELD NAME="highestattemptsavg" TYPE="number" LENGTH="15" NOTNULL="false" SEQUENCE="false" DECIMALS="5"/>
<FIELD NAME="lastattemptsavg" TYPE="number" LENGTH="15" NOTNULL="false" SEQUENCE="false" DECIMALS="5"/>
<FIELD NAME="allattemptsavg" TYPE="number" LENGTH="15" NOTNULL="false" SEQUENCE="false" DECIMALS="5"/>
<FIELD NAME="median" TYPE="number" LENGTH="15" NOTNULL="false" SEQUENCE="false" DECIMALS="5"/>
<FIELD NAME="standarddeviation" TYPE="number" LENGTH="15" NOTNULL="false" SEQUENCE="false" DECIMALS="5"/>
+45
View File
@@ -85,6 +85,51 @@ function xmldb_quiz_statistics_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2013092000, 'quiz', 'statistics');
}
if ($oldversion < 2013093000) {
// Define table quiz_statistics to be dropped.
$table = new xmldb_table('quiz_statistics');
// Conditionally launch drop table for quiz_statistics.
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
// Define table quiz_statistics to be created.
$table = new xmldb_table('quiz_statistics');
// Adding fields to table quiz_statistics.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('hashcode', XMLDB_TYPE_CHAR, '40', null, XMLDB_NOTNULL, null, null);
$table->add_field('whichattempts', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, null);
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('firstattemptscount', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('highestattemptscount', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('lastattemptscount', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('allattemptscount', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('firstattemptsavg', XMLDB_TYPE_NUMBER, '15, 5', null, null, null, null);
$table->add_field('highestattemptsavg', XMLDB_TYPE_NUMBER, '15, 5', null, null, null, null);
$table->add_field('lastattemptsavg', XMLDB_TYPE_NUMBER, '15, 5', null, null, null, null);
$table->add_field('allattemptsavg', XMLDB_TYPE_NUMBER, '15, 5', null, null, null, null);
$table->add_field('median', XMLDB_TYPE_NUMBER, '15, 5', null, null, null, null);
$table->add_field('standarddeviation', XMLDB_TYPE_NUMBER, '15, 5', null, null, null, null);
$table->add_field('skewness', XMLDB_TYPE_NUMBER, '15, 10', null, null, null, null);
$table->add_field('kurtosis', XMLDB_TYPE_NUMBER, '15, 5', null, null, null, null);
$table->add_field('cic', XMLDB_TYPE_NUMBER, '15, 10', null, null, null, null);
$table->add_field('errorratio', XMLDB_TYPE_NUMBER, '15, 10', null, null, null, null);
$table->add_field('standarderror', XMLDB_TYPE_NUMBER, '15, 10', null, null, null, null);
// Adding keys to table quiz_statistics.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
// Conditionally launch create table for quiz_statistics.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Statistics savepoint reached.
upgrade_plugin_savepoint(true, 2013093000, 'quiz', 'statistics');
}
return true;
}
@@ -56,8 +56,12 @@ $string['firstattempts'] = 'first attempts';
$string['firstattemptsavg'] = 'Average grade of first attempts';
$string['firstattemptscount'] = 'Number of complete graded first attempts';
$string['frequency'] = 'Frequency';
$string['highestattempts'] = 'highest graded attempt';
$string['highestattemptsavg'] = 'Average grade of highest graded attempts';
$string['intended_weight'] = 'Intended weight';
$string['kurtosis'] = 'Score distribution kurtosis (for {$a})';
$string['lastattempts'] = 'last attempt';
$string['lastattemptsavg'] = 'Average grade of last attempts';
$string['lastcalculated'] = 'Last calculated {$a->lastcalculated} ago there have been {$a->count} attempts since then.';
$string['median'] = 'Median grade (for {$a})';
$string['modelresponse'] = 'Model response';
@@ -85,6 +89,7 @@ $string['quizoverallstatistics'] = 'Quiz overall statistics';
$string['quizstructureanalysis'] = 'Quiz structure analysis';
$string['random_guess_score'] = 'Random guess score';
$string['recalculatenow'] = 'Recalculate now';
$string['reportsettings'] = 'Statistics calculation settings';
$string['response'] = 'Response';
$string['skewness'] = 'Score distribution skewness (for {$a})';
$string['standarddeviation'] = 'Standard deviation (for {$a})';
+34 -36
View File
@@ -61,6 +61,7 @@ class quiz_statistics_report extends quiz_default_report {
// 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);
$whichattempts = optional_param('whichattempts', $quiz->grademethod, PARAM_INT);
$pageoptions = array();
$pageoptions['id'] = $cm->id;
@@ -69,17 +70,15 @@ class quiz_statistics_report extends quiz_default_report {
$reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions);
$mform = new quiz_statistics_settings_form($reporturl);
if ($fromform = $mform->get_data()) {
$useallattempts = $fromform->useallattempts;
if ($fromform->useallattempts) {
set_user_preference('quiz_report_statistics_useallattempts',
$fromform->useallattempts);
} else {
unset_user_preference('quiz_report_statistics_useallattempts');
}
} else {
$useallattempts = get_user_preferences('quiz_report_statistics_useallattempts', 0);
$mform->set_data(array('whichattempts' => $whichattempts));
if ($fromform = $mform->get_data()) {
$whichattempts = $fromform->whichattempts;
}
if ($whichattempts != $quiz->grademethod) {
$reporturl->param('whichattempts', $whichattempts);
}
// Find out current groups mode.
@@ -103,7 +102,7 @@ class quiz_statistics_report extends quiz_default_report {
}
}
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $currentgroup, $groupstudents, $useallattempts);
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
// If recalculate was requested, handle that.
if ($recalculate && confirm_sesskey()) {
@@ -128,10 +127,10 @@ class quiz_statistics_report extends quiz_default_report {
if (!$nostudentsingroup) {
// Get the data to be displayed.
list($quizstats, $questionstats, $subquestionstats) =
$this->get_quiz_and_questions_stats($quiz, $currentgroup, $useallattempts, $groupstudents, $questions);
$this->get_quiz_and_questions_stats($quiz, $whichattempts, $groupstudents, $questions);
} else {
// Or create empty stats containers.
$quizstats = new quiz_statistics_calculated($useallattempts);
$quizstats = new quiz_statistics_calculated($whichattempts);
$questionstats = array();
$subquestionstats = array();
}
@@ -159,7 +158,6 @@ class quiz_statistics_report extends quiz_default_report {
}
// Print display options form.
$mform->set_data(array('useallattempts' => $useallattempts));
$mform->display();
}
@@ -172,7 +170,7 @@ class quiz_statistics_report extends quiz_default_report {
$this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
if ($this->table->is_downloading() == 'xhtml' && $quizstats->s() != 0) {
$this->output_statistics_graph($quiz->id, $currentgroup, $useallattempts);
$this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
}
foreach ($questions as $slot => $question) {
@@ -232,15 +230,14 @@ class quiz_statistics_report extends quiz_default_report {
} else {
// On-screen display of overview report.
echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'));
echo $this->output_caching_info($quizstats, $quiz->id, $currentgroup,
$groupstudents, $useallattempts, $reporturl);
echo $this->output_caching_info($quizstats, $quiz->id, $groupstudents, $whichattempts, $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'));
$this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
$this->output_statistics_graph($quiz->id, $currentgroup, $useallattempts);
$this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
}
}
@@ -471,14 +468,14 @@ class quiz_statistics_report extends quiz_default_report {
* Output the HTML needed to show the statistics graph.
* @param $quizid
* @param $currentgroup
* @param $useallattempts
* @param $whichattempts
*/
protected function output_statistics_graph($quizid, $currentgroup, $useallattempts) {
protected function output_statistics_graph($quizid, $currentgroup, $whichattempts) {
global $PAGE;
$output = $PAGE->get_renderer('mod_quiz');
$imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
compact('quizid', 'currentgroup', 'useallattempts'));
compact('quizid', 'currentgroup', 'whichattempts'));
$graphname = get_string('statisticsreportgraph', 'quiz_statistics');
echo $output->graph($imageurl, $graphname);
}
@@ -488,18 +485,20 @@ class quiz_statistics_report extends quiz_default_report {
* or by recomputing them.
*
* @param object $quiz the quiz settings.
* @param int $currentgroup the current group. 0 for none.
* @param bool $useallattempts use all attempts, or just first attempts.
* @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student.
* @param array $groupstudents students in this group.
* @param array $questions question definitions.
* @param array $questions full question data.
* @return array with 4 elements:
* - $quizstats The statistics for overall attempt scores.
* - $questionstats array of \core_question\statistics\questions\calculated objects keyed by slot.
* - $subquestionstats array of \core_question\statistics\questions\calculated_for_subquestion objects keyed by question id.
*/
protected function get_quiz_and_questions_stats($quiz, $currentgroup, $useallattempts, $groupstudents, $questions) {
protected function get_quiz_and_questions_stats($quiz, $whichattempts, $groupstudents, $questions) {
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $currentgroup, $groupstudents, $useallattempts);
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
$qcalc = new \core_question\statistics\questions\calculator($questions);
@@ -509,8 +508,8 @@ class quiz_statistics_report extends quiz_default_report {
// Recalculate now.
list($questionstats, $subquestionstats) = $qcalc->calculate($qubaids);
$quizstats = $quizcalc->calculate($quiz->id, $currentgroup, $useallattempts,
$groupstudents, count($questions), $qcalc->get_sum_of_mark_variance());
$quizstats = $quizcalc->calculate($quiz->id, $whichattempts, $groupstudents, count($questions),
$qcalc->get_sum_of_mark_variance());
if ($quizstats->s()) {
$this->analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats);
@@ -574,16 +573,16 @@ class quiz_statistics_report extends quiz_default_report {
* with a recalcuate now button.
* @param object $quizstats the overall quiz statistics.
* @param int $quizid the quiz id.
* @param int $currentgroup the id of the currently selected group, or 0.
* @param array $groupstudents ids of students in the group.
* @param bool $useallattempts whether to use all attempts, instead of just
* first attempts.
* @param array $groupstudents ids of students in the group or empty array if groups not used.
* @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student.
* @param moodle_url $reporturl url for this report
* @return string a HTML snipped saying when the stats were last computed,
* or blank if that is not appropriate.
*/
protected function output_caching_info($quizstats, $quizid, $currentgroup,
$groupstudents, $useallattempts, $reporturl) {
protected function output_caching_info($quizstats, $quizid, $groupstudents, $whichattempts, $reporturl) {
global $DB, $OUTPUT;
if (empty($quizstats->timemodified)) {
@@ -591,8 +590,7 @@ class quiz_statistics_report extends quiz_default_report {
}
// Find the number of attempts since the cached statistics were computed.
list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql(
$quizid, $currentgroup, $groupstudents, $useallattempts, true);
list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts, true);
$count = $DB->count_records_sql("
SELECT COUNT(1)
FROM $fromqa
@@ -36,17 +36,15 @@ class quiz_statistics_settings_form extends moodleform {
protected function definition() {
$mform = $this->_form;
$mform->addElement('header', 'preferencespage',
get_string('preferencespage', 'quiz_overview'));
$mform->addElement('header', 'preferencespage', get_string('reportsettings', 'quiz_statistics'));
$options = array();
$options[0] = get_string('attemptsfirst', 'quiz_statistics');
$options[1] = get_string('attemptsall', 'quiz_statistics');
$mform->addElement('select', 'useallattempts',
get_string('calculatefrom', 'quiz_statistics'), $options);
$mform->setDefault('useallattempts', 0);
foreach (array_keys(quiz_get_grading_options()) as $which) {
$options[$which] = \quiz_statistics_calculator::using_attempts_lang_string($which);
}
$mform->addElement('submit', 'submitbutton',
get_string('preferencessave', 'quiz_overview'));
$mform->addElement('select', 'whichattempts', get_string('calculatefrom', 'quiz_statistics'), $options);
$mform->addElement('submit', 'submitbutton', get_string('preferencessave', 'quiz_overview'));
}
}
@@ -37,7 +37,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
// Get the parameters.
$quizid = required_param('quizid', PARAM_INT);
$currentgroup = required_param('currentgroup', PARAM_INT);
$useallattempts = required_param('useallattempts', PARAM_INT);
$whichattempts = required_param('whichattempts', PARAM_INT);
$quiz = $DB->get_record('quiz', array('id' => $quizid), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('quiz', $quiz->id);
@@ -55,10 +55,14 @@ if (groups_get_activity_groupmode($cm)) {
if ($currentgroup && !in_array($currentgroup, array_keys($groups))) {
print_error('groupnotamember', 'group');
}
$groupstudents = get_users_by_capability($modcontext, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
'', '', '', '', $currentgroup, '', false);
$qubaids = quiz_statistics_qubaids_condition($quizid, $currentgroup, $groupstudents, $useallattempts);
if (empty($currentgroup)) {
$groupstudents = array();
} else {
$groupstudents = get_users_by_capability($modcontext, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
'', '', '', '', $currentgroup, '', false);
}
$qubaids = quiz_statistics_qubaids_condition($quizid, $groupstudents, $whichattempts);
// Load the rest of the required data.
$questions = quiz_report_get_significant_questions($quiz);
+24 -11
View File
@@ -23,8 +23,19 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function quiz_statistics_attempts_sql($quizid, $currentgroup, $groupstudents,
$allattempts = true, $includeungraded = false) {
/**
* SQL to fetch relevant 'quiz_attempts' records.
*
* @param int $quizid quiz id to get attempts for
* @param array $groupstudents empty array if not using groups or array of students in current group.
* @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student.
* @param bool $includeungraded whether to fetch ungraded attempts too
* @return array FROM and WHERE sql fragments and sql params
*/
function quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts = QUIZ_GRADEAVERAGE, $includeungraded = false) {
global $DB;
$fromqa = '{quiz_attempts} quiza ';
@@ -32,15 +43,17 @@ function quiz_statistics_attempts_sql($quizid, $currentgroup, $groupstudents,
$whereqa = 'quiza.quiz = :quizid AND quiza.preview = 0 AND quiza.state = :quizstatefinished';
$qaparams = array('quizid' => (int)$quizid, 'quizstatefinished' => quiz_attempt::FINISHED);
if (!empty($currentgroup) && $groupstudents) {
if ($groupstudents) {
ksort($groupstudents);
list($grpsql, $grpparams) = $DB->get_in_or_equal(array_keys($groupstudents),
SQL_PARAMS_NAMED, 'u');
$whereqa .= " AND quiza.userid $grpsql";
$qaparams += $grpparams;
}
if (!$allattempts) {
$whereqa .= ' AND quiza.attempt = 1';
$whichattemptsql = quiz_report_grade_method_sql($whichattempts);
if ($whichattemptsql) {
$whereqa .= ' AND '.$whichattemptsql;
}
if (!$includeungraded) {
@@ -54,16 +67,16 @@ function quiz_statistics_attempts_sql($quizid, $currentgroup, $groupstudents,
* Return a {@link qubaid_condition} from the values returned by {@link quiz_statistics_attempts_sql}.
*
* @param int $quizid
* @param int $currentgroup
* @param array $groupstudents
* @param bool $allattempts
* @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
* $quiz->grademethod ie.
* QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
* we calculate stats based on which attempts would affect the grade for each student.
* @param bool $includeungraded
* @return \qubaid_join
*/
function quiz_statistics_qubaids_condition($quizid, $currentgroup, $groupstudents,
$allattempts = true, $includeungraded = false) {
list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $currentgroup,
$groupstudents, $allattempts, $includeungraded);
function quiz_statistics_qubaids_condition($quizid, $groupstudents, $whichattempts = QUIZ_GRADEAVERAGE, $includeungraded = false) {
list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts, $includeungraded);
return new qubaid_join($fromqa, 'quiza.uniqueid', $whereqa, $qaparams);
}
@@ -40,12 +40,11 @@ require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
*/
class testable_quiz_statistics_report extends quiz_statistics_report {
public function get_stats($quiz, $useallattempts = true,
$currentgroup = 0, $groupstudents = array(), $nostudentsingroup = false) {
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $currentgroup, $groupstudents, $useallattempts);
public function get_stats($quiz, $whichattempts = QUIZ_GRADEAVERAGE, $groupstudents = array()) {
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
$this->clear_cached_data($qubaids);
$questions = $this->load_and_initialise_questions_for_calculations($quiz);
return $this->get_quiz_and_questions_stats($quiz, $currentgroup, $useallattempts, $groupstudents, $questions);
return $this->get_quiz_and_questions_stats($quiz, $whichattempts, $groupstudents, $questions);
}
}
+2 -2
View File
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2013092000;
$plugin->requires = 2013092000;
$plugin->version = 2013093000;
$plugin->requires = 2013092700;
$plugin->cron = 18000;
$plugin->component = 'quiz_statistics';