Merge branch 'MDL-30704_22' of git://github.com/timhunt/moodle into MOODLE_22_STABLE
This commit is contained in:
@@ -121,7 +121,7 @@ abstract class quiz_attempt_report extends quiz_default_report {
|
||||
protected function validate_common_options(&$attemptsmode, &$pagesize, $course, $currentgroup) {
|
||||
if ($currentgroup) {
|
||||
//default for when a group is selected
|
||||
if ($attemptsmode === null || $attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) {
|
||||
if ($attemptsmode === null || $attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) {
|
||||
$attemptsmode = QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH;
|
||||
}
|
||||
} else if (!$currentgroup && $course->id == SITEID) {
|
||||
@@ -137,94 +137,6 @@ abstract class quiz_attempt_report extends quiz_default_report {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contruct all the parts of the main database query.
|
||||
* @param object $quiz the quiz settings.
|
||||
* @param string $qmsubselect SQL fragment from {@link quiz_report_qm_filter_select()}.
|
||||
* @param bool $qmfilter whether to show all, or only the final grade attempt.
|
||||
* @param int $attemptsmode which attempts to show.
|
||||
* One of the QUIZ_REPORT_ATTEMPTS_... constants.
|
||||
* @param array $reportstudents list if userids of users to include in the report.
|
||||
* @return array with 4 elements ($fields, $from, $where, $params) that can be used to
|
||||
* build the actual database query.
|
||||
*/
|
||||
protected function base_sql($quiz, $qmsubselect, $qmfilter, $attemptsmode, $reportstudents) {
|
||||
global $DB;
|
||||
|
||||
$fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') . ' AS uniqueid,';
|
||||
|
||||
if ($qmsubselect) {
|
||||
$fields .= "\n(CASE WHEN $qmsubselect THEN 1 ELSE 0 END) AS gradedattempt,";
|
||||
}
|
||||
|
||||
$extrafields = get_extra_user_fields_sql($this->context, 'u', '',
|
||||
array('id', 'idnumber', 'firstname', 'lastname', 'picture',
|
||||
'imagealt', 'institution', 'department', 'email'));
|
||||
$fields .= '
|
||||
quiza.uniqueid AS usageid,
|
||||
quiza.id AS attempt,
|
||||
u.id AS userid,
|
||||
u.idnumber,
|
||||
u.firstname,
|
||||
u.lastname,
|
||||
u.picture,
|
||||
u.imagealt,
|
||||
u.institution,
|
||||
u.department,
|
||||
u.email' . $extrafields . ',
|
||||
quiza.sumgrades,
|
||||
quiza.timefinish,
|
||||
quiza.timestart,
|
||||
CASE WHEN quiza.timefinish = 0 THEN null
|
||||
WHEN quiza.timefinish > quiza.timestart THEN quiza.timefinish - quiza.timestart
|
||||
ELSE 0 END AS duration';
|
||||
// To explain that last bit, in MySQL, qa.timestart and qa.timefinish
|
||||
// are unsigned. Since MySQL 5.5.5, when they introduced strict mode,
|
||||
// subtracting a larger unsigned int from a smaller one gave an error.
|
||||
// Therefore, we avoid doing that. timefinish can be non-zero and less
|
||||
// than timestart when you have two load-balanced servers with very
|
||||
// badly synchronised clocks, and a student does a really quick attempt.';
|
||||
|
||||
// This part is the same for all cases - join users and quiz_attempts tables
|
||||
$from = "\n{user} u";
|
||||
$from .= "\nLEFT JOIN {quiz_attempts} quiza ON
|
||||
quiza.userid = u.id AND quiza.quiz = :quizid";
|
||||
$params = array('quizid' => $quiz->id);
|
||||
|
||||
if ($qmsubselect && $qmfilter) {
|
||||
$from .= " AND $qmsubselect";
|
||||
}
|
||||
switch ($attemptsmode) {
|
||||
case QUIZ_REPORT_ATTEMPTS_ALL:
|
||||
// Show all attempts, including students who are no longer in the course
|
||||
$where = 'quiza.id IS NOT NULL AND quiza.preview = 0';
|
||||
break;
|
||||
case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH:
|
||||
// Show only students with attempts
|
||||
list($usql, $uparams) = $DB->get_in_or_equal(
|
||||
$reportstudents, SQL_PARAMS_NAMED, 'u');
|
||||
$params += $uparams;
|
||||
$where = "u.id $usql AND quiza.preview = 0 AND quiza.id IS NOT NULL";
|
||||
break;
|
||||
case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
|
||||
// Show only students without attempts
|
||||
list($usql, $uparams) = $DB->get_in_or_equal(
|
||||
$reportstudents, SQL_PARAMS_NAMED, 'u');
|
||||
$params += $uparams;
|
||||
$where = "u.id $usql AND quiza.id IS NULL";
|
||||
break;
|
||||
case QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS:
|
||||
// Show all students with or without attempts
|
||||
list($usql, $uparams) = $DB->get_in_or_equal(
|
||||
$reportstudents, SQL_PARAMS_NAMED, 'u');
|
||||
$params += $uparams;
|
||||
$where = "u.id $usql AND (quiza.preview = 0 OR quiza.preview IS NULL)";
|
||||
break;
|
||||
}
|
||||
|
||||
return array($fields, $from, $where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the user-related columns to the $columns and $headers arrays.
|
||||
* @param table_sql $table the table being constructed.
|
||||
@@ -401,17 +313,22 @@ abstract class quiz_attempt_report_table extends table_sql {
|
||||
protected $quiz;
|
||||
protected $context;
|
||||
protected $qmsubselect;
|
||||
protected $qmfilter;
|
||||
protected $attemptsmode;
|
||||
protected $groupstudents;
|
||||
protected $students;
|
||||
protected $questions;
|
||||
protected $includecheckboxes;
|
||||
|
||||
public function __construct($uniqueid, $quiz, $context, $qmsubselect, $groupstudents,
|
||||
$students, $questions, $includecheckboxes, $reporturl, $displayoptions) {
|
||||
public function __construct($uniqueid, $quiz, $context, $qmsubselect, $qmfilter,
|
||||
$attemptsmode, $groupstudents, $students, $questions, $includecheckboxes,
|
||||
$reporturl, $displayoptions) {
|
||||
parent::__construct($uniqueid);
|
||||
$this->quiz = $quiz;
|
||||
$this->context = $context;
|
||||
$this->qmsubselect = $qmsubselect;
|
||||
$this->qmfilter = $qmfilter;
|
||||
$this->attemptsmode = $attemptsmode;
|
||||
$this->groupstudents = $groupstudents;
|
||||
$this->students = $students;
|
||||
$this->questions = $questions;
|
||||
@@ -609,6 +526,89 @@ abstract class quiz_attempt_report_table extends table_sql {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Contruct all the parts of the main database query.
|
||||
* @param array $reportstudents list if userids of users to include in the report.
|
||||
* @return array with 4 elements ($fields, $from, $where, $params) that can be used to
|
||||
* build the actual database query.
|
||||
*/
|
||||
public function base_sql($reportstudents) {
|
||||
global $DB;
|
||||
|
||||
$fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') . ' AS uniqueid,';
|
||||
|
||||
if ($this->qmsubselect) {
|
||||
$fields .= "\n(CASE WHEN $this->qmsubselect THEN 1 ELSE 0 END) AS gradedattempt,";
|
||||
}
|
||||
|
||||
$extrafields = get_extra_user_fields_sql($this->context, 'u', '',
|
||||
array('id', 'idnumber', 'firstname', 'lastname', 'picture',
|
||||
'imagealt', 'institution', 'department', 'email'));
|
||||
$fields .= '
|
||||
quiza.uniqueid AS usageid,
|
||||
quiza.id AS attempt,
|
||||
u.id AS userid,
|
||||
u.idnumber,
|
||||
u.firstname,
|
||||
u.lastname,
|
||||
u.picture,
|
||||
u.imagealt,
|
||||
u.institution,
|
||||
u.department,
|
||||
u.email' . $extrafields . ',
|
||||
quiza.sumgrades,
|
||||
quiza.timefinish,
|
||||
quiza.timestart,
|
||||
CASE WHEN quiza.timefinish = 0 THEN null
|
||||
WHEN quiza.timefinish > quiza.timestart THEN quiza.timefinish - quiza.timestart
|
||||
ELSE 0 END AS duration';
|
||||
// To explain that last bit, in MySQL, qa.timestart and qa.timefinish
|
||||
// are unsigned. Since MySQL 5.5.5, when they introduced strict mode,
|
||||
// subtracting a larger unsigned int from a smaller one gave an error.
|
||||
// Therefore, we avoid doing that. timefinish can be non-zero and less
|
||||
// than timestart when you have two load-balanced servers with very
|
||||
// badly synchronised clocks, and a student does a really quick attempt.';
|
||||
|
||||
// This part is the same for all cases - join users and quiz_attempts tables
|
||||
$from = "\n{user} u";
|
||||
$from .= "\nLEFT JOIN {quiz_attempts} quiza ON
|
||||
quiza.userid = u.id AND quiza.quiz = :quizid";
|
||||
$params = array('quizid' => $this->quiz->id);
|
||||
|
||||
if ($this->qmsubselect && $this->qmfilter) {
|
||||
$from .= " AND $this->qmsubselect";
|
||||
}
|
||||
switch ($this->attemptsmode) {
|
||||
case QUIZ_REPORT_ATTEMPTS_ALL:
|
||||
// Show all attempts, including students who are no longer in the course
|
||||
$where = 'quiza.id IS NOT NULL AND quiza.preview = 0';
|
||||
break;
|
||||
case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH:
|
||||
// Show only students with attempts
|
||||
list($usql, $uparams) = $DB->get_in_or_equal(
|
||||
$reportstudents, SQL_PARAMS_NAMED, 'u');
|
||||
$params += $uparams;
|
||||
$where = "u.id $usql AND quiza.preview = 0 AND quiza.id IS NOT NULL";
|
||||
break;
|
||||
case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
|
||||
// Show only students without attempts
|
||||
list($usql, $uparams) = $DB->get_in_or_equal(
|
||||
$reportstudents, SQL_PARAMS_NAMED, 'u');
|
||||
$params += $uparams;
|
||||
$where = "u.id $usql AND quiza.id IS NULL";
|
||||
break;
|
||||
case QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS:
|
||||
// Show all students with or without attempts
|
||||
list($usql, $uparams) = $DB->get_in_or_equal(
|
||||
$reportstudents, SQL_PARAMS_NAMED, 'u');
|
||||
$params += $uparams;
|
||||
$where = "u.id $usql AND (quiza.preview = 0 OR quiza.preview IS NULL)";
|
||||
break;
|
||||
}
|
||||
|
||||
return array($fields, $from, $where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the information about the latest state of the question with slot
|
||||
* $slot to the query.
|
||||
|
||||
@@ -37,86 +37,101 @@ class quiz_report_overview_table extends quiz_attempt_report_table {
|
||||
|
||||
protected $regradedqs = array();
|
||||
|
||||
public function __construct($quiz, $context, $qmsubselect, $groupstudents,
|
||||
$students, $detailedmarks, $questions, $includecheckboxes, $reporturl, $displayoptions) {
|
||||
public function __construct($quiz, $context, $qmsubselect, $qmfilter,
|
||||
$attemptsmode, $groupstudents, $students, $detailedmarks,
|
||||
$questions, $includecheckboxes, $reporturl, $displayoptions) {
|
||||
parent::__construct('mod-quiz-report-overview-report', $quiz , $context,
|
||||
$qmsubselect, $groupstudents, $students, $questions, $includecheckboxes,
|
||||
$reporturl, $displayoptions);
|
||||
$qmsubselect, $qmfilter, $attemptsmode, $groupstudents, $students,
|
||||
$questions, $includecheckboxes, $reporturl, $displayoptions);
|
||||
$this->detailedmarks = $detailedmarks;
|
||||
}
|
||||
|
||||
public function build_table() {
|
||||
global $DB;
|
||||
|
||||
if ($this->rawdata) {
|
||||
$this->strtimeformat = str_replace(',', '', get_string('strftimedatetime'));
|
||||
parent::build_table();
|
||||
if (!$this->rawdata) {
|
||||
return;
|
||||
}
|
||||
|
||||
//end of adding data from attempts data to table / download
|
||||
//now add averages at bottom of table :
|
||||
$params = array($this->quiz->id);
|
||||
$averagesql = '
|
||||
SELECT AVG(qg.grade) AS grade, COUNT(qg.grade) AS numaveraged
|
||||
FROM {quiz_grades} qg
|
||||
WHERE quiz = ?';
|
||||
$this->strtimeformat = str_replace(',', '', get_string('strftimedatetime'));
|
||||
parent::build_table();
|
||||
|
||||
$this->add_separator();
|
||||
if ($this->is_downloading()) {
|
||||
$namekey = 'lastname';
|
||||
} else {
|
||||
$namekey = 'fullname';
|
||||
}
|
||||
if ($this->groupstudents) {
|
||||
list($usql, $uparams) = $DB->get_in_or_equal($this->groupstudents);
|
||||
$record = $DB->get_record_sql($averagesql . ' AND qg.userid ' . $usql,
|
||||
array_merge($params, $uparams));
|
||||
$groupaveragerow = array(
|
||||
$namekey => get_string('groupavg', 'grades'),
|
||||
'sumgrades' => $this->format_average($record),
|
||||
'feedbacktext'=> strip_tags(quiz_report_feedback_for_grade(
|
||||
$record->grade, $this->quiz->id, $this->context)));
|
||||
if ($this->detailedmarks && ($this->quiz->attempts == 1 || $this->qmsubselect)) {
|
||||
$avggradebyq = $this->load_average_question_grades($this->groupstudents);
|
||||
$groupaveragerow += $this->format_average_grade_for_questions($avggradebyq);
|
||||
}
|
||||
$this->add_data_keyed($groupaveragerow);
|
||||
}
|
||||
// End of adding the data from attempts. Now add averages at bottom.
|
||||
$this->add_separator();
|
||||
|
||||
if ($this->students) {
|
||||
list($usql, $uparams) = $DB->get_in_or_equal($this->students);
|
||||
$record = $DB->get_record_sql($averagesql . ' AND qg.userid ' . $usql,
|
||||
array_merge($params, $uparams));
|
||||
$overallaveragerow = array(
|
||||
$namekey => get_string('overallaverage', 'grades'),
|
||||
'sumgrades' => $this->format_average($record),
|
||||
'feedbacktext'=> strip_tags(quiz_report_feedback_for_grade(
|
||||
$record->grade, $this->quiz->id, $this->context)));
|
||||
if ($this->detailedmarks && ($this->quiz->attempts == 1 || $this->qmsubselect)) {
|
||||
$avggradebyq = $this->load_average_question_grades($this->students);
|
||||
$overallaveragerow += $this->format_average_grade_for_questions($avggradebyq);
|
||||
}
|
||||
$this->add_data_keyed($overallaveragerow);
|
||||
}
|
||||
if ($this->groupstudents) {
|
||||
$this->add_average_row(get_string('groupavg', 'grades'), $this->groupstudents);
|
||||
}
|
||||
|
||||
if ($this->students) {
|
||||
$this->add_average_row(get_string('overallaverage', 'grades'), $this->students);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an average grade over the attempts of a set of users.
|
||||
* @param string $label the title ot use for this row.
|
||||
* @param array $users the users to average over.
|
||||
*/
|
||||
protected function add_average_row($label, $users) {
|
||||
global $DB;
|
||||
|
||||
list($fields, $from, $where, $params) = $this->base_sql($users);
|
||||
$record = $DB->get_record_sql("
|
||||
SELECT AVG(quiza.sumgrades) AS grade, COUNT(quiza.sumgrades) AS numaveraged
|
||||
FROM $from
|
||||
WHERE $where", $params);
|
||||
|
||||
if ($this->is_downloading()) {
|
||||
$namekey = 'lastname';
|
||||
} else {
|
||||
$namekey = 'fullname';
|
||||
}
|
||||
$averagerow = array(
|
||||
$namekey => $label,
|
||||
'sumgrades' => $this->format_average($record),
|
||||
'feedbacktext'=> strip_tags(quiz_report_feedback_for_grade(
|
||||
$record->grade, $this->quiz->id, $this->context))
|
||||
);
|
||||
|
||||
if ($this->detailedmarks) {
|
||||
$dm = new question_engine_data_mapper();
|
||||
$qubaids = new qubaid_join($from, 'quiza.uniqueid', $where, $params);
|
||||
$avggradebyq = $dm->load_average_marks($qubaids, array_keys($this->questions));
|
||||
|
||||
$averagerow += $this->format_average_grade_for_questions($avggradebyq);
|
||||
}
|
||||
|
||||
$this->add_data_keyed($averagerow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper userd by {@link add_average_row()}.
|
||||
* @param array $gradeaverages the raw grades.
|
||||
* @return array the (partial) row of data.
|
||||
*/
|
||||
protected function format_average_grade_for_questions($gradeaverages) {
|
||||
$row = array();
|
||||
|
||||
if (!$gradeaverages) {
|
||||
$gradeaverages = array();
|
||||
}
|
||||
|
||||
foreach ($this->questions as $question) {
|
||||
if (isset($gradeaverages[$question->slot]) && $question->maxmark > 0) {
|
||||
$record = $gradeaverages[$question->slot];
|
||||
$record->grade = quiz_rescale_grade(
|
||||
$record->averagefraction * $question->maxmark, $this->quiz, false);
|
||||
|
||||
} else {
|
||||
$record = new stdClass();
|
||||
$record->grade = null;
|
||||
$record->numaveraged = null;
|
||||
}
|
||||
|
||||
$row['qsgrade' . $question->slot] = $this->format_average($record, true);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
@@ -276,30 +291,6 @@ class quiz_report_overview_table extends quiz_attempt_report_table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the average grade for each question, averaged over particular users.
|
||||
* @param array $userids the user ids to average over.
|
||||
*/
|
||||
protected function load_average_question_grades($userids) {
|
||||
global $DB;
|
||||
|
||||
$qmfilter = '';
|
||||
if ($this->quiz->attempts != 1) {
|
||||
$qmfilter = '(' . quiz_report_qm_filter_select($this->quiz, 'quiza') . ') AND ';
|
||||
}
|
||||
|
||||
list($usql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'u');
|
||||
$params['quizid'] = $this->quiz->id;
|
||||
$qubaids = new qubaid_join(
|
||||
'{quiz_attempts} quiza',
|
||||
'quiza.uniqueid',
|
||||
"quiza.userid $usql AND quiza.quiz = :quizid",
|
||||
$params);
|
||||
|
||||
$dm = new question_engine_data_mapper();
|
||||
return $dm->load_average_marks($qubaids, array_keys($this->questions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the questions in all the attempts being displayed that need regrading.
|
||||
* @return array A two dimensional array $questionusageid => $slot => $regradeinfo.
|
||||
|
||||
@@ -129,8 +129,8 @@ class quiz_overview_report extends quiz_attempt_report {
|
||||
$questions = quiz_report_get_significant_questions($quiz);
|
||||
|
||||
$table = new quiz_report_overview_table($quiz, $this->context, $qmsubselect,
|
||||
$groupstudents, $students, $detailedmarks, $questions, $includecheckboxes,
|
||||
$reporturl, $displayoptions);
|
||||
$qmfilter, $attemptsmode, $groupstudents, $students, $detailedmarks,
|
||||
$questions, $includecheckboxes, $reporturl, $displayoptions);
|
||||
$filename = quiz_report_download_filename(get_string('overviewfilename', 'quiz_overview'),
|
||||
$courseshortname, $quiz->name);
|
||||
$table->is_downloading($download, $filename,
|
||||
@@ -219,8 +219,7 @@ class quiz_overview_report extends quiz_attempt_report {
|
||||
"END) AS gradedattempt, ";
|
||||
}
|
||||
|
||||
list($fields, $from, $where, $params) =
|
||||
$this->base_sql($quiz, $qmsubselect, $qmfilter, $attemptsmode, $allowed);
|
||||
list($fields, $from, $where, $params) = $table->base_sql($allowed);
|
||||
|
||||
$table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params);
|
||||
|
||||
|
||||
@@ -146,7 +146,8 @@ class quiz_responses_report extends quiz_attempt_report {
|
||||
array('context' => $displaycoursecontext));
|
||||
|
||||
$table = new quiz_report_responses_table($quiz, $this->context, $qmsubselect,
|
||||
$groupstudents, $students, $questions, $includecheckboxes, $reporturl, $displayoptions);
|
||||
$qmfilter, $attemptsmode, $groupstudents, $students, $questions,
|
||||
$includecheckboxes, $reporturl, $displayoptions);
|
||||
$filename = quiz_report_download_filename(get_string('responsesfilename', 'quiz_responses'),
|
||||
$courseshortname, $quiz->name);
|
||||
$table->is_downloading($download, $filename,
|
||||
@@ -197,8 +198,7 @@ class quiz_responses_report extends quiz_attempt_report {
|
||||
}
|
||||
}
|
||||
|
||||
list($fields, $from, $where, $params) =
|
||||
$this->base_sql($quiz, $qmsubselect, $qmfilter, $attemptsmode, $allowed);
|
||||
list($fields, $from, $where, $params) = $table->base_sql($allowed);
|
||||
|
||||
$table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params);
|
||||
|
||||
|
||||
@@ -35,11 +35,12 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class quiz_report_responses_table extends quiz_attempt_report_table {
|
||||
|
||||
public function __construct($quiz, $context, $qmsubselect, $groupstudents,
|
||||
$students, $questions, $includecheckboxes, $reporturl, $displayoptions) {
|
||||
public function __construct($quiz, $context, $qmsubselect, $qmfilter,
|
||||
$attemptsmode, $groupstudents, $students,
|
||||
$questions, $includecheckboxes, $reporturl, $displayoptions) {
|
||||
parent::__construct('mod-quiz-report-responses-report', $quiz, $context,
|
||||
$qmsubselect, $groupstudents, $students, $questions, $includecheckboxes,
|
||||
$reporturl, $displayoptions);
|
||||
$qmsubselect, $qmfilter, $attemptsmode, $groupstudents, $students,
|
||||
$questions, $includecheckboxes, $reporturl, $displayoptions);
|
||||
}
|
||||
|
||||
public function build_table() {
|
||||
|
||||
Reference in New Issue
Block a user