From 7d5564d959d33b1b00d585513200496bbc68b63f Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Tue, 24 Jan 2017 11:03:42 +0100 Subject: [PATCH 1/2] MDL-57754 mod_lesson: Move overview report code to function Move the table and statistics generation to a new function --- mod/lesson/locallib.php | 335 ++++++++++++++++++++++++++++++++++++++ mod/lesson/report.php | 350 ++++------------------------------------ 2 files changed, 362 insertions(+), 323 deletions(-) diff --git a/mod/lesson/locallib.php b/mod/lesson/locallib.php index 1bfaf096c10..1a1f6e630ae 100644 --- a/mod/lesson/locallib.php +++ b/mod/lesson/locallib.php @@ -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 = ' '; + } else { + $temp = ''; + } + + $temp .= "id&action=reportdetail&userid=".$try['userid'] + .'&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 .= " ".userdate($try["timestart"]); + $temp .= ", (".format_time($timetotake).")"; + } else { + if ($try["end"]) { + // User finished the lesson but has no grade. (Happens when there are only content pages). + $temp .= " ".userdate($try["timestart"]); + $timetotake = $try["timeend"] - $try["timestart"]; + $temp .= ", (".format_time($timetotake).")"; + } 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 .= " ".userdate($try["timestart"]); + } + $temp .= ""; + $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("
\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. * diff --git a/mod/lesson/report.php b/mod/lesson/report.php index df158a58ffc..1aa2258beac 100644 --- a/mod/lesson/report.php +++ b/mod/lesson/report.php @@ -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 = ' '; - } else { - $temp = ''; - } - - $temp .= "id&action=reportdetail&userid=".$try['userid'] - .'&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 .= " ".userdate($try["timestart"]); - $temp .= ", (".format_time($timetotake).")"; - } else { - if ($try["end"]) { - // User finished the lesson but has no grade. (Happens when there are only content pages). - $temp .= " ".userdate($try["timestart"]); - $timetotake = $try["timeend"] - $try["timestart"]; - $temp .= ", (".format_time($timetotake).")"; - } 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 .= " ".userdate($try["timestart"]); - } - $temp .= ""; - $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("
\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 "
\n \n id\" />\n"; } + echo html_writer::table($table); + if (has_capability('mod/lesson:edit', $context)) { $checklinks = ''.get_string('selectall').' / '; $checklinks .= ''.get_string('deselectall').''; @@ -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); From 899b000d999b7ee87f03a2896a44fbb7a8f0f16f Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Tue, 24 Jan 2017 11:36:28 +0100 Subject: [PATCH 2/2] MDL-57754 mod_lesson: New Web Service mod_lesson_get_attempts_overview --- mod/lesson/classes/external.php | 111 +++++++++++++++++++ mod/lesson/db/services.php | 8 ++ mod/lesson/tests/external_test.php | 166 +++++++++++++++++++---------- mod/lesson/version.php | 2 +- 4 files changed, 230 insertions(+), 57 deletions(-) diff --git a/mod/lesson/classes/external.php b/mod/lesson/classes/external.php index 8cd19d8e289..411a2030f76 100644 --- a/mod/lesson/classes/external.php +++ b/mod/lesson/classes/external.php @@ -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(), + ) + ); + } } diff --git a/mod/lesson/db/services.php b/mod/lesson/db/services.php index 4e1c87456e8..30fa487c4c5 100644 --- a/mod/lesson/db/services.php +++ b/mod/lesson/db/services.php @@ -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) + ), ); diff --git a/mod/lesson/tests/external_test.php b/mod/lesson/tests/external_test.php index 7a98c609eca..bd1f7ae9d91 100644 --- a/mod/lesson/tests/external_test.php +++ b/mod/lesson/tests/external_test.php @@ -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']); + } } diff --git a/mod/lesson/version.php b/mod/lesson/version.php index dc0ab41da2e..4ca26ce03dd 100644 --- a/mod/lesson/version.php +++ b/mod/lesson/version.php @@ -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;