Merge branch 'MDL-57754-master' of git://github.com/jleyva/moodle

This commit is contained in:
David Monllao
2017-03-27 11:43:44 +02:00
6 changed files with 592 additions and 380 deletions
+111
View File
@@ -1650,4 +1650,115 @@ class mod_lesson_external extends external_api {
)
);
}
/**
* Describes the parameters for get_attempts_overview.
*
* @return external_external_function_parameters
* @since Moodle 3.3
*/
public static function get_attempts_overview_parameters() {
return new external_function_parameters (
array(
'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
'groupid' => new external_value(PARAM_INT, 'group id, 0 means that the function will determine the user group',
VALUE_DEFAULT, 0),
)
);
}
/**
* Get a list of all the attempts made by users in a lesson.
*
* @param int $lessonid lesson instance id
* @param int $groupid group id, 0 means that the function will determine the user group
* @return array of warnings and status result
* @since Moodle 3.3
* @throws moodle_exception
*/
public static function get_attempts_overview($lessonid, $groupid = 0) {
$params = array('lessonid' => $lessonid, 'groupid' => $groupid);
$params = self::validate_parameters(self::get_attempts_overview_parameters(), $params);
$studentsdata = $warnings = array();
list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
require_capability('mod/lesson:viewreports', $context);
if (!empty($params['groupid'])) {
$groupid = $params['groupid'];
// Determine is the group is visible to user.
if (!groups_group_visible($groupid, $course, $cm)) {
throw new moodle_exception('notingroup');
}
} else {
// Check to see if groups are being used here.
if ($groupmode = groups_get_activity_groupmode($cm)) {
$groupid = groups_get_activity_group($cm);
// Determine is the group is visible to user (this is particullary for the group 0 -> all groups).
if (!groups_group_visible($groupid, $course, $cm)) {
throw new moodle_exception('notingroup');
}
} else {
$groupid = 0;
}
}
list($table, $data) = lesson_get_overview_report_table_and_data($lesson, $groupid);
if ($data !== false) {
$studentsdata = $data;
}
$result = array(
'data' => $studentsdata,
'warnings' => $warnings
);
return $result;
}
/**
* Describes the get_attempts_overview return value.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function get_attempts_overview_returns() {
return new external_single_structure(
array(
'data' => new external_single_structure(
array(
'lessonscored' => new external_value(PARAM_BOOL, 'True if the lesson was scored.'),
'numofattempts' => new external_value(PARAM_INT, 'Number of attempts.'),
'avescore' => new external_value(PARAM_FLOAT, 'Average score.'),
'highscore' => new external_value(PARAM_FLOAT, 'High score.'),
'lowscore' => new external_value(PARAM_FLOAT, 'Low score.'),
'avetime' => new external_value(PARAM_INT, 'Average time (spent in taking the lesson).'),
'hightime' => new external_value(PARAM_INT, 'High time.'),
'lowtime' => new external_value(PARAM_INT, 'Low time.'),
'students' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'User id.'),
'fullname' => new external_value(PARAM_TEXT, 'User full name.'),
'bestgrade' => new external_value(PARAM_FLOAT, 'Best grade.'),
'attempts' => new external_multiple_structure(
new external_single_structure(
array(
'try' => new external_value(PARAM_INT, 'Attempt number.'),
'grade' => new external_value(PARAM_FLOAT, 'Attempt grade.'),
'timestart' => new external_value(PARAM_INT, 'Attempt time started.'),
'timeend' => new external_value(PARAM_INT, 'Attempt last time continued.'),
'end' => new external_value(PARAM_INT, 'Attempt time ended.'),
)
)
)
)
), 'Students data, including attempts.', VALUE_OPTIONAL
),
)
),
'warnings' => new external_warnings(),
)
);
}
}
+8
View File
@@ -132,4 +132,12 @@ $functions = array(
'capabilities' => 'mod/lesson:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_lesson_get_attempts_overview' => array(
'classname' => 'mod_lesson_external',
'methodname' => 'get_attempts_overview',
'description' => 'Get a list of all the attempts made by users in a lesson.',
'type' => 'read',
'capabilities' => 'mod/lesson:viewreports',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
);
+335
View File
@@ -674,6 +674,341 @@ function lesson_process_group_deleted_in_course($courseid, $groupid = null) {
$DB->delete_records_list('lesson_overrides', 'id', array_keys($records));
}
/**
* Return the overview report table and data.
*
* @param lesson $lesson lesson instance
* @param mixed $currentgroup false if not group used, 0 for all groups, group id (int) to filter by that groups
* @return mixed false if there is no information otherwise html_table and stdClass with the table and data
* @since Moodle 3.3
*/
function lesson_get_overview_report_table_and_data(lesson $lesson, $currentgroup) {
global $DB;
$context = $lesson->context;
$cm = $lesson->cm;
// Count the number of branch and question pages in this lesson.
$branchcount = $DB->count_records('lesson_pages', array('lessonid' => $lesson->id, 'qtype' => LESSON_PAGE_BRANCHTABLE));
$questioncount = ($DB->count_records('lesson_pages', array('lessonid' => $lesson->id)) - $branchcount);
// Only load students if there attempts for this lesson.
$attempts = $DB->record_exists('lesson_attempts', array('lessonid' => $lesson->id));
$branches = $DB->record_exists('lesson_branch', array('lessonid' => $lesson->id));
$timer = $DB->record_exists('lesson_timer', array('lessonid' => $lesson->id));
if ($attempts or $branches or $timer) {
list($esql, $params) = get_enrolled_sql($context, '', $currentgroup, true);
list($sort, $sortparams) = users_order_by_sql('u');
$params['a1lessonid'] = $lesson->id;
$params['b1lessonid'] = $lesson->id;
$params['c1lessonid'] = $lesson->id;
$ufields = user_picture::fields('u');
$sql = "SELECT DISTINCT $ufields
FROM {user} u
JOIN (
SELECT userid, lessonid FROM {lesson_attempts} a1
WHERE a1.lessonid = :a1lessonid
UNION
SELECT userid, lessonid FROM {lesson_branch} b1
WHERE b1.lessonid = :b1lessonid
UNION
SELECT userid, lessonid FROM {lesson_timer} c1
WHERE c1.lessonid = :c1lessonid
) a ON u.id = a.userid
JOIN ($esql) ue ON ue.id = a.userid
ORDER BY $sort";
$students = $DB->get_recordset_sql($sql, $params);
if (!$students->valid()) {
$students->close();
return array(false, false);
}
} else {
return array(false, false);
}
if (! $grades = $DB->get_records('lesson_grades', array('lessonid' => $lesson->id), 'completed')) {
$grades = array();
}
if (! $times = $DB->get_records('lesson_timer', array('lessonid' => $lesson->id), 'starttime')) {
$times = array();
}
// Build an array for output.
$studentdata = array();
$attempts = $DB->get_recordset('lesson_attempts', array('lessonid' => $lesson->id), 'timeseen');
foreach ($attempts as $attempt) {
// if the user is not in the array or if the retry number is not in the sub array, add the data for that try.
if (empty($studentdata[$attempt->userid]) || empty($studentdata[$attempt->userid][$attempt->retry])) {
// restore/setup defaults
$n = 0;
$timestart = 0;
$timeend = 0;
$usergrade = null;
$eol = false;
// search for the grade record for this try. if not there, the nulls defined above will be used.
foreach($grades as $grade) {
// check to see if the grade matches the correct user
if ($grade->userid == $attempt->userid) {
// see if n is = to the retry
if ($n == $attempt->retry) {
// get grade info
$usergrade = round($grade->grade, 2); // round it here so we only have to do it once
break;
}
$n++; // if not equal, then increment n
}
}
$n = 0;
// search for the time record for this try. if not there, the nulls defined above will be used.
foreach($times as $time) {
// check to see if the grade matches the correct user
if ($time->userid == $attempt->userid) {
// see if n is = to the retry
if ($n == $attempt->retry) {
// get grade info
$timeend = $time->lessontime;
$timestart = $time->starttime;
$eol = $time->completed;
break;
}
$n++; // if not equal, then increment n
}
}
// build up the array.
// this array represents each student and all of their tries at the lesson
$studentdata[$attempt->userid][$attempt->retry] = array( "timestart" => $timestart,
"timeend" => $timeend,
"grade" => $usergrade,
"end" => $eol,
"try" => $attempt->retry,
"userid" => $attempt->userid);
}
}
$attempts->close();
$branches = $DB->get_recordset('lesson_branch', array('lessonid' => $lesson->id), 'timeseen');
foreach ($branches as $branch) {
// If the user is not in the array or if the retry number is not in the sub array, add the data for that try.
if (empty($studentdata[$branch->userid]) || empty($studentdata[$branch->userid][$branch->retry])) {
// Restore/setup defaults.
$n = 0;
$timestart = 0;
$timeend = 0;
$usergrade = null;
$eol = false;
// Search for the time record for this try. if not there, the nulls defined above will be used.
foreach ($times as $time) {
// Check to see if the grade matches the correct user.
if ($time->userid == $branch->userid) {
// See if n is = to the retry.
if ($n == $branch->retry) {
// Get grade info.
$timeend = $time->lessontime;
$timestart = $time->starttime;
$eol = $time->completed;
break;
}
$n++; // If not equal, then increment n.
}
}
// Build up the array.
// This array represents each student and all of their tries at the lesson.
$studentdata[$branch->userid][$branch->retry] = array( "timestart" => $timestart,
"timeend" => $timeend,
"grade" => $usergrade,
"end" => $eol,
"try" => $branch->retry,
"userid" => $branch->userid);
}
}
$branches->close();
// Need the same thing for timed entries that were not completed.
foreach ($times as $time) {
$endoflesson = $time->completed;
// If the time start is the same with another record then we shouldn't be adding another item to this array.
if (isset($studentdata[$time->userid])) {
$foundmatch = false;
$n = 0;
foreach ($studentdata[$time->userid] as $key => $value) {
if ($value['timestart'] == $time->starttime) {
// Don't add this to the array.
$foundmatch = true;
break;
}
}
$n = count($studentdata[$time->userid]) + 1;
if (!$foundmatch) {
// Add a record.
$studentdata[$time->userid][] = array(
"timestart" => $time->starttime,
"timeend" => $time->lessontime,
"grade" => null,
"end" => $endoflesson,
"try" => $n,
"userid" => $time->userid
);
}
} else {
$studentdata[$time->userid][] = array(
"timestart" => $time->starttime,
"timeend" => $time->lessontime,
"grade" => null,
"end" => $endoflesson,
"try" => 0,
"userid" => $time->userid
);
}
}
// To store all the data to be returned by the function.
$data = new stdClass();
// Determine if lesson should have a score.
if ($branchcount > 0 AND $questioncount == 0) {
// This lesson only contains content pages and is not graded.
$data->lessonscored = false;
} else {
// This lesson is graded.
$data->lessonscored = true;
}
// set all the stats variables
$data->numofattempts = 0;
$data->avescore = 0;
$data->avetime = 0;
$data->highscore = null;
$data->lowscore = null;
$data->hightime = null;
$data->lowtime = null;
$data->students = array();
$table = new html_table();
// Set up the table object.
if ($data->lessonscored) {
$table->head = array(get_string('name'), get_string('attempts', 'lesson'), get_string('highscore', 'lesson'));
} else {
$table->head = array(get_string('name'), get_string('attempts', 'lesson'));
}
$table->align = array('center', 'left', 'left');
$table->wrap = array('nowrap', 'nowrap', 'nowrap');
$table->attributes['class'] = 'standardtable generaltable';
$table->size = array(null, '70%', null);
// print out the $studentdata array
// going through each student that has attempted the lesson, so, each student should have something to be displayed
foreach ($students as $student) {
// check to see if the student has attempts to print out
if (array_key_exists($student->id, $studentdata)) {
// set/reset some variables
$attempts = array();
$dataforstudent = new stdClass;
$dataforstudent->attempts = array();
// gather the data for each user attempt
$bestgrade = 0;
$bestgradefound = false;
// $tries holds all the tries/retries a student has done
$tries = $studentdata[$student->id];
$studentname = fullname($student, true);
foreach ($tries as $try) {
$dataforstudent->attempts[] = $try;
// Start to build up the checkbox and link.
if (has_capability('mod/lesson:edit', $context)) {
$temp = '<input type="checkbox" id="attempts" name="attempts['.$try['userid'].']['.$try['try'].']" /> ';
} else {
$temp = '';
}
$temp .= "<a href=\"report.php?id=$cm->id&amp;action=reportdetail&amp;userid=".$try['userid']
.'&amp;try='.$try['try'].'" class="lesson-attempt-link">';
if ($try["grade"] !== null) { // if null then not done yet
// this is what the link does when the user has completed the try
$timetotake = $try["timeend"] - $try["timestart"];
$temp .= $try["grade"]."%";
$bestgradefound = true;
if ($try["grade"] > $bestgrade) {
$bestgrade = $try["grade"];
}
$temp .= "&nbsp;".userdate($try["timestart"]);
$temp .= ",&nbsp;(".format_time($timetotake).")</a>";
} else {
if ($try["end"]) {
// User finished the lesson but has no grade. (Happens when there are only content pages).
$temp .= "&nbsp;".userdate($try["timestart"]);
$timetotake = $try["timeend"] - $try["timestart"];
$temp .= ",&nbsp;(".format_time($timetotake).")</a>";
} else {
// This is what the link does/looks like when the user has not completed the attempt.
$temp .= get_string("notcompleted", "lesson");
if ($try['timestart'] !== 0) {
// Teacher previews do not track time spent.
$temp .= "&nbsp;".userdate($try["timestart"]);
}
$temp .= "</a>";
$timetotake = null;
}
}
// build up the attempts array
$attempts[] = $temp;
// Run these lines for the stats only if the user finnished the lesson.
if ($try["end"]) {
// User has completed the lesson.
$data->numofattempts++;
$data->avetime += $timetotake;
if ($timetotake > $data->hightime || $data->hightime == null) {
$data->hightime = $timetotake;
}
if ($timetotake < $data->lowtime || $data->lowtime == null) {
$data->lowtime = $timetotake;
}
if ($try["grade"] !== null) {
// The lesson was scored.
$data->avescore += $try["grade"];
if ($try["grade"] > $data->highscore || $data->highscore === null) {
$data->highscore = $try["grade"];
}
if ($try["grade"] < $data->lowscore || $data->lowscore === null) {
$data->lowscore = $try["grade"];
}
}
}
}
// get line breaks in after each attempt
$attempts = implode("<br />\n", $attempts);
if ($data->lessonscored) {
// Add the grade if the lesson is graded.
$table->data[] = array($studentname, $attempts, $bestgrade . "%");
} else {
// This lesson does not have a grade.
$table->data[] = array($studentname, $attempts);
}
// Add the student data.
$dataforstudent->id = $student->id;
$dataforstudent->fullname = $studentname;
$dataforstudent->bestgrade = $bestgrade;
$data->students[] = $dataforstudent;
}
}
$students->close();
if ($data->numofattempts > 0) {
$data->avescore = $data->avescore / $data->numofattempts;
}
return array($table, $data);
}
/**
* Abstract class that page type's MUST inherit from.
*
+27 -323
View File
@@ -114,47 +114,10 @@ if ($action === 'delete') {
this action is for default view and overview view
**************************************************************************/
// Count the number of branch and question pages in this lesson.
$branchcount = $DB->count_records('lesson_pages', array('lessonid' => $lesson->id, 'qtype' => LESSON_PAGE_BRANCHTABLE));
$questioncount = ($DB->count_records('lesson_pages', array('lessonid' => $lesson->id)) - $branchcount);
// Get the table and data for build statistics.
list($table, $data) = lesson_get_overview_report_table_and_data($lesson, $currentgroup);
// Only load students if there attempts for this lesson.
$attempts = $DB->record_exists('lesson_attempts', array('lessonid' => $lesson->id));
$branches = $DB->record_exists('lesson_branch', array('lessonid' => $lesson->id));
$timer = $DB->record_exists('lesson_timer', array('lessonid' => $lesson->id));
if ($attempts or $branches or $timer) {
list($esql, $params) = get_enrolled_sql($context, '', $currentgroup, true);
list($sort, $sortparams) = users_order_by_sql('u');
$params['a1lessonid'] = $lesson->id;
$params['b1lessonid'] = $lesson->id;
$params['c1lessonid'] = $lesson->id;
$ufields = user_picture::fields('u');
$sql = "SELECT DISTINCT $ufields
FROM {user} u
JOIN (
SELECT userid, lessonid FROM {lesson_attempts} a1
WHERE a1.lessonid = :a1lessonid
UNION
SELECT userid, lessonid FROM {lesson_branch} b1
WHERE b1.lessonid = :b1lessonid
UNION
SELECT userid, lessonid FROM {lesson_timer} c1
WHERE c1.lessonid = :c1lessonid
) a ON u.id = a.userid
JOIN ($esql) ue ON ue.id = a.userid
ORDER BY $sort";
$students = $DB->get_recordset_sql($sql, $params);
if (!$students->valid()) {
$students->close();
$nothingtodisplay = true;
}
} else {
$nothingtodisplay = true;
}
if ($nothingtodisplay) {
if ($table === false) {
echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('nolessonattempts', 'lesson'));
if (!empty($currentgroup)) {
$groupname = groups_get_group_name($currentgroup);
@@ -167,14 +130,6 @@ if ($action === 'delete') {
exit();
}
if (! $grades = $DB->get_records('lesson_grades', array('lessonid' => $lesson->id), 'completed')) {
$grades = array();
}
if (! $times = $DB->get_records('lesson_timer', array('lessonid' => $lesson->id), 'starttime')) {
$times = array();
}
echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('overview', 'lesson'));
groups_print_activity_menu($cm, $url);
@@ -185,266 +140,15 @@ if ($action === 'delete') {
echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
}
// Build an array for output.
$studentdata = array();
$attempts = $DB->get_recordset('lesson_attempts', array('lessonid' => $lesson->id), 'timeseen');
foreach ($attempts as $attempt) {
// if the user is not in the array or if the retry number is not in the sub array, add the data for that try.
if (empty($studentdata[$attempt->userid]) || empty($studentdata[$attempt->userid][$attempt->retry])) {
// restore/setup defaults
$n = 0;
$timestart = 0;
$timeend = 0;
$usergrade = null;
$eol = false;
// search for the grade record for this try. if not there, the nulls defined above will be used.
foreach($grades as $grade) {
// check to see if the grade matches the correct user
if ($grade->userid == $attempt->userid) {
// see if n is = to the retry
if ($n == $attempt->retry) {
// get grade info
$usergrade = round($grade->grade, 2); // round it here so we only have to do it once
break;
}
$n++; // if not equal, then increment n
}
}
$n = 0;
// search for the time record for this try. if not there, the nulls defined above will be used.
foreach($times as $time) {
// check to see if the grade matches the correct user
if ($time->userid == $attempt->userid) {
// see if n is = to the retry
if ($n == $attempt->retry) {
// get grade info
$timeend = $time->lessontime;
$timestart = $time->starttime;
$eol = $time->completed;
break;
}
$n++; // if not equal, then increment n
}
}
// build up the array.
// this array represents each student and all of their tries at the lesson
$studentdata[$attempt->userid][$attempt->retry] = array( "timestart" => $timestart,
"timeend" => $timeend,
"grade" => $usergrade,
"end" => $eol,
"try" => $attempt->retry,
"userid" => $attempt->userid);
}
}
$attempts->close();
$branches = $DB->get_recordset('lesson_branch', array('lessonid' => $lesson->id), 'timeseen');
foreach ($branches as $branch) {
// If the user is not in the array or if the retry number is not in the sub array, add the data for that try.
if (empty($studentdata[$branch->userid]) || empty($studentdata[$branch->userid][$branch->retry])) {
// Restore/setup defaults.
$n = 0;
$timestart = 0;
$timeend = 0;
$usergrade = null;
$eol = false;
// Search for the time record for this try. if not there, the nulls defined above will be used.
foreach ($times as $time) {
// Check to see if the grade matches the correct user.
if ($time->userid == $branch->userid) {
// See if n is = to the retry.
if ($n == $branch->retry) {
// Get grade info.
$timeend = $time->lessontime;
$timestart = $time->starttime;
$eol = $time->completed;
break;
}
$n++; // If not equal, then increment n.
}
}
// Build up the array.
// This array represents each student and all of their tries at the lesson.
$studentdata[$branch->userid][$branch->retry] = array( "timestart" => $timestart,
"timeend" => $timeend,
"grade" => $usergrade,
"end" => $eol,
"try" => $branch->retry,
"userid" => $branch->userid);
}
}
$branches->close();
// Need the same thing for timed entries that were not completed.
foreach ($times as $time) {
$endoflesson = $time->completed;
// If the time start is the same with another record then we shouldn't be adding another item to this array.
if (isset($studentdata[$time->userid])) {
$foundmatch = false;
$n = 0;
foreach ($studentdata[$time->userid] as $key => $value) {
if ($value['timestart'] == $time->starttime) {
// Don't add this to the array.
$foundmatch = true;
break;
}
}
$n = count($studentdata[$time->userid]) + 1;
if (!$foundmatch) {
// Add a record.
$studentdata[$time->userid][] = array(
"timestart" => $time->starttime,
"timeend" => $time->lessontime,
"grade" => null,
"end" => $endoflesson,
"try" => $n,
"userid" => $time->userid
);
}
} else {
$studentdata[$time->userid][] = array(
"timestart" => $time->starttime,
"timeend" => $time->lessontime,
"grade" => null,
"end" => $endoflesson,
"try" => 0,
"userid" => $time->userid
);
}
}
// Determine if lesson should have a score.
if ($branchcount > 0 AND $questioncount == 0) {
// This lesson only contains content pages and is not graded.
$lessonscored = false;
} else {
// This lesson is graded.
$lessonscored = true;
}
// set all the stats variables
$numofattempts = 0;
$avescore = 0;
$avetime = 0;
$highscore = null;
$lowscore = null;
$hightime = null;
$lowtime = null;
$table = new html_table();
// Set up the table object.
if ($lessonscored) {
$table->head = array(get_string('name'), get_string('attempts', 'lesson'), get_string('highscore', 'lesson'));
} else {
$table->head = array(get_string('name'), get_string('attempts', 'lesson'));
}
$table->align = array('center', 'left', 'left');
$table->wrap = array('nowrap', 'nowrap', 'nowrap');
$table->attributes['class'] = 'standardtable generaltable';
$table->size = array(null, '70%', null);
// print out the $studentdata array
// going through each student that has attempted the lesson, so, each student should have something to be displayed
foreach ($students as $student) {
// check to see if the student has attempts to print out
if (array_key_exists($student->id, $studentdata)) {
// set/reset some variables
$attempts = array();
// gather the data for each user attempt
$bestgrade = 0;
$bestgradefound = false;
// $tries holds all the tries/retries a student has done
$tries = $studentdata[$student->id];
$studentname = fullname($student, true);
foreach ($tries as $try) {
// start to build up the checkbox and link
if (has_capability('mod/lesson:edit', $context)) {
$temp = '<input type="checkbox" id="attempts" name="attempts['.$try['userid'].']['.$try['try'].']" /> ';
} else {
$temp = '';
}
$temp .= "<a href=\"report.php?id=$cm->id&amp;action=reportdetail&amp;userid=".$try['userid']
.'&amp;try='.$try['try'].'" class="lesson-attempt-link">';
if ($try["grade"] !== null) { // if null then not done yet
// this is what the link does when the user has completed the try
$timetotake = $try["timeend"] - $try["timestart"];
$temp .= $try["grade"]."%";
$bestgradefound = true;
if ($try["grade"] > $bestgrade) {
$bestgrade = $try["grade"];
}
$temp .= "&nbsp;".userdate($try["timestart"]);
$temp .= ",&nbsp;(".format_time($timetotake).")</a>";
} else {
if ($try["end"]) {
// User finished the lesson but has no grade. (Happens when there are only content pages).
$temp .= "&nbsp;".userdate($try["timestart"]);
$timetotake = $try["timeend"] - $try["timestart"];
$temp .= ",&nbsp;(".format_time($timetotake).")</a>";
} else {
// This is what the link does/looks like when the user has not completed the attempt.
$temp .= get_string("notcompleted", "lesson");
if ($try['timestart'] !== 0) {
// Teacher previews do not track time spent.
$temp .= "&nbsp;".userdate($try["timestart"]);
}
$temp .= "</a>";
$timetotake = null;
}
}
// build up the attempts array
$attempts[] = $temp;
// Run these lines for the stats only if the user finnished the lesson.
if ($try["end"]) {
// User has completed the lesson.
$numofattempts++;
$avetime += $timetotake;
if ($timetotake > $hightime || $hightime == null) {
$hightime = $timetotake;
}
if ($timetotake < $lowtime || $lowtime == null) {
$lowtime = $timetotake;
}
if ($try["grade"] !== null) {
// The lesson was scored.
$avescore += $try["grade"];
if ($try["grade"] > $highscore || $highscore === null) {
$highscore = $try["grade"];
}
if ($try["grade"] < $lowscore || $lowscore === null) {
$lowscore = $try["grade"];
}
}
}
}
// get line breaks in after each attempt
$attempts = implode("<br />\n", $attempts);
if ($lessonscored) {
// Add the grade if the lesson is graded.
$bestgrade = $bestgrade."%";
$table->data[] = array($studentname, $attempts, $bestgrade);
} else {
// This lesson does not have a grade.
$table->data[] = array($studentname, $attempts);
}
}
}
$students->close();
// Print it all out!
if (has_capability('mod/lesson:edit', $context)) {
echo "<form id=\"mod-lesson-report-form\" method=\"post\" action=\"report.php\">\n
<input type=\"hidden\" name=\"sesskey\" value=\"".sesskey()."\" />\n
<input type=\"hidden\" name=\"id\" value=\"$cm->id\" />\n";
}
echo html_writer::table($table);
if (has_capability('mod/lesson:edit', $context)) {
$checklinks = '<a id="checkall" href="#">'.get_string('selectall').'</a> / ';
$checklinks .= '<a id="checknone" href="#">'.get_string('deselectall').'</a>';
@@ -471,38 +175,38 @@ if ($action === 'delete') {
}
// Calculate the Statistics.
if ($avetime == null) {
$avetime = get_string("notcompleted", "lesson");
if ($data->avetime == null) {
$data->avetime = get_string("notcompleted", "lesson");
} else {
$avetime = format_float($avetime/$numofattempts, 0);
$avetime = format_time($avetime);
$data->avetime = format_float($data->avetime / $data->numofattempts, 0);
$data->avetime = format_time($data->avetime);
}
if ($hightime == null) {
$hightime = get_string("notcompleted", "lesson");
if ($data->hightime == null) {
$data->hightime = get_string("notcompleted", "lesson");
} else {
$hightime = format_time($hightime);
$data->hightime = format_time($data->hightime);
}
if ($lowtime == null) {
$lowtime = get_string("notcompleted", "lesson");
if ($data->lowtime == null) {
$data->lowtime = get_string("notcompleted", "lesson");
} else {
$lowtime = format_time($lowtime);
$data->lowtime = format_time($data->lowtime);
}
if ($lessonscored) {
if ($numofattempts == 0) {
$avescore = get_string("notcompleted", "lesson");
if ($data->lessonscored) {
if ($data->numofattempts == 0) {
$data->avescore = get_string("notcompleted", "lesson");
} else {
$avescore = format_float($avescore / $numofattempts, 2) . '%';
$data->avescore = format_float($data->avescore, 2) . '%';
}
if ($highscore === null) {
$highscore = get_string("notcompleted", "lesson");
if ($data->highscore === null) {
$data->highscore = get_string("notcompleted", "lesson");
} else {
$highscore .= '%';
$data->highscore .= '%';
}
if ($lowscore === null) {
$lowscore = get_string("notcompleted", "lesson");
if ($data->lowscore === null) {
$data->lowscore = get_string("notcompleted", "lesson");
} else {
$lowscore .= '%';
$data->lowscore .= '%';
}
// Display the full stats for the lesson.
@@ -514,7 +218,7 @@ if ($action === 'delete') {
$stattable->align = array('center', 'center', 'center', 'center', 'center', 'center');
$stattable->wrap = array('nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap');
$stattable->attributes['class'] = 'standardtable generaltable';
$stattable->data[] = array($avescore, $avetime, $highscore, $lowscore, $hightime, $lowtime);
$stattable->data[] = array($data->avescore, $data->avetime, $data->highscore, $data->lowscore, $data->hightime, $data->lowtime);
} else {
// Display simple stats for the lesson.
@@ -525,7 +229,7 @@ if ($action === 'delete') {
$stattable->align = array('center', 'center', 'center');
$stattable->wrap = array('nowrap', 'nowrap', 'nowrap');
$stattable->attributes['class'] = 'standardtable generaltable';
$stattable->data[] = array($avetime, $hightime, $lowtime);
$stattable->data[] = array($data->avetime, $data->hightime, $data->lowtime);
}
echo html_writer::table($stattable);
+110 -56
View File
@@ -976,26 +976,28 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
}
/**
* Test process_page
* Creates an attempt for the given userwith a correct or incorrect answer and optionally finishes it.
*
* @param stdClass $user Create an attempt for this user
* @param boolean $correct If the answer should be correct
* @param boolean $finished If we should finish the attempt
* @return array the result of the attempt creation or finalisation
*/
public function test_process_page() {
protected function create_attempt($user, $correct = true, $finished = false) {
global $DB;
$this->setUser($this->student);
$this->setUser($user);
// First we need to launch the lesson so the timer is on.
mod_lesson_external::launch_attempt($this->lesson->id);
// Configure the lesson to return feedback and avoid custom scoring.
$DB->set_field('lesson', 'feedback', 1, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'progressbar', 1, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'custom', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'maxattempts', 3, array('id' => $this->lesson->id));
// Now, we can directly launch mocking the data.
// First incorrect response.
$answerincorrect = 0;
$answercorrect = 0;
$answerincorrect = 0;
$p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id');
foreach ($p2answers as $answer) {
if ($answer->jumpto == 0) {
@@ -1008,7 +1010,7 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
$data = array(
array(
'name' => 'answerid',
'value' => $answerincorrect,
'value' => $correct ? $answercorrect : $answerincorrect,
),
array(
'name' => '_qf__lesson_display_answer_form_truefalse',
@@ -1018,24 +1020,28 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
$result = mod_lesson_external::process_page($this->lesson->id, $this->page2->id, $data);
$result = external_api::clean_returnvalue(mod_lesson_external::process_page_returns(), $result);
if ($finished) {
$result = mod_lesson_external::finish_attempt($this->lesson->id);
$result = external_api::clean_returnvalue(mod_lesson_external::finish_attempt_returns(), $result);
}
return $result;
}
/**
* Test process_page
*/
public function test_process_page() {
global $DB;
// Attempt first with incorrect response.
$result = $this->create_attempt($this->student, false, false);
$this->assertEquals($this->page2->id, $result['newpageid']); // Same page, since the answer was incorrect.
$this->assertFalse($result['correctanswer']); // Incorrect answer.
$this->assertEquals(50, $result['progress']);
// Correct response.
$data = array(
array(
'name' => 'answerid',
'value' => $answercorrect,
),
array(
'name' => '_qf__lesson_display_answer_form_truefalse',
'value' => 1,
)
);
$result = mod_lesson_external::process_page($this->lesson->id, $this->page2->id, $data);
$result = external_api::clean_returnvalue(mod_lesson_external::process_page_returns(), $result);
// Attempt with correct response.
$result = $this->create_attempt($this->student, true, false);
$this->assertEquals($this->page1->id, $result['newpageid']); // Next page, the answer was correct.
$this->assertTrue($result['correctanswer']); // Correct response.
@@ -1078,39 +1084,8 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
* Test finish attempt with correct answer.
*/
public function test_finish_attempt_with_correct_answer() {
global $DB;
$this->setUser($this->student);
// First we need to launch the lesson so the timer is on.
mod_lesson_external::launch_attempt($this->lesson->id);
// Attempt a question, correct answer.
$DB->set_field('lesson', 'custom', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'progressbar', 1, array('id' => $this->lesson->id));
$answercorrect = 0;
$p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id');
foreach ($p2answers as $answer) {
if ($answer->jumpto != 0) {
$answercorrect = $answer->id;
}
}
$data = array(
array(
'name' => 'answerid',
'value' => $answercorrect,
),
array(
'name' => '_qf__lesson_display_answer_form_truefalse',
'value' => 1,
)
);
$result = mod_lesson_external::process_page($this->lesson->id, $this->page2->id, $data);
$result = external_api::clean_returnvalue(mod_lesson_external::process_page_returns(), $result);
$result = mod_lesson_external::finish_attempt($this->lesson->id);
$result = external_api::clean_returnvalue(mod_lesson_external::finish_attempt_returns(), $result);
// Create a finished attempt.
$result = $this->create_attempt($this->student, true, true);
$this->assertCount(0, $result['warnings']);
$returneddata = [];
@@ -1131,4 +1106,83 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
'manualpoints' => 0,
];
}
/**
* Test get_attempts_overview
*/
public function test_get_attempts_overview() {
global $DB;
// Create a finished attempt with incorrect answer.
$this->setCurrentTimeStart();
$this->create_attempt($this->student, false, true);
$this->setAdminUser();
$result = mod_lesson_external::get_attempts_overview($this->lesson->id);
$result = external_api::clean_returnvalue(mod_lesson_external::get_attempts_overview_returns(), $result);
// One attempt, 0 for grade (incorrect response) in overal statistics.
$this->assertEquals(1, $result['data']['numofattempts']);
$this->assertEquals(0, $result['data']['avescore']);
$this->assertEquals(0, $result['data']['highscore']);
$this->assertEquals(0, $result['data']['lowscore']);
// Check one student, finished attempt, 0 for grade.
$this->assertCount(1, $result['data']['students']);
$this->assertEquals($this->student->id, $result['data']['students'][0]['id']);
$this->assertEquals(0, $result['data']['students'][0]['bestgrade']);
$this->assertCount(1, $result['data']['students'][0]['attempts']);
$this->assertEquals(1, $result['data']['students'][0]['attempts'][0]['end']);
$this->assertEquals(0, $result['data']['students'][0]['attempts'][0]['grade']);
$this->assertTimeCurrent($result['data']['students'][0]['attempts'][0]['timestart']);
$this->assertTimeCurrent($result['data']['students'][0]['attempts'][0]['timeend']);
// Add a new attempt (same user).
sleep(1);
// Allow first retake.
$DB->set_field('lesson', 'retake', 1, array('id' => $this->lesson->id));
// Create a finished attempt with correct answer.
$this->setCurrentTimeStart();
$this->create_attempt($this->student, true, true);
$this->setAdminUser();
$result = mod_lesson_external::get_attempts_overview($this->lesson->id);
$result = external_api::clean_returnvalue(mod_lesson_external::get_attempts_overview_returns(), $result);
// Two attempts with maximum grade.
$this->assertEquals(2, $result['data']['numofattempts']);
$this->assertEquals(50.00, format_float($result['data']['avescore'], 2));
$this->assertEquals(100, $result['data']['highscore']);
$this->assertEquals(0, $result['data']['lowscore']);
// Check one student, finished two attempts, 100 for final grade.
$this->assertCount(1, $result['data']['students']);
$this->assertEquals($this->student->id, $result['data']['students'][0]['id']);
$this->assertEquals(100, $result['data']['students'][0]['bestgrade']);
$this->assertCount(2, $result['data']['students'][0]['attempts']);
foreach ($result['data']['students'][0]['attempts'] as $attempt) {
if ($attempt['try'] == 0) {
// First attempt, 0 for grade.
$this->assertEquals(0, $attempt['grade']);
} else {
$this->assertEquals(100, $attempt['grade']);
}
}
// Now, add other user failed attempt.
$student2 = self::getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student2->id, $this->course->id, $this->studentrole->id, 'manual');
$this->create_attempt($student2, false, true);
// Now check we have two students and the statistics changed.
$this->setAdminUser();
$result = mod_lesson_external::get_attempts_overview($this->lesson->id);
$result = external_api::clean_returnvalue(mod_lesson_external::get_attempts_overview_returns(), $result);
// Total of 3 attempts with maximum grade.
$this->assertEquals(3, $result['data']['numofattempts']);
$this->assertEquals(33.33, format_float($result['data']['avescore'], 2));
$this->assertEquals(100, $result['data']['highscore']);
$this->assertEquals(0, $result['data']['lowscore']);
// Check students.
$this->assertCount(2, $result['data']['students']);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2016120512; // The current module version (Date: YYYYMMDDXX)
$plugin->version = 2016120513; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2016112900; // Requires this Moodle version
$plugin->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;