completion: MDL-2631 Improve queries, reports and fix problems checking for tracked users

This commit is contained in:
Aaron Barnes 2010-08-12 03:40:35 +00:00
parent 465a9b4f06
commit 24a3b34131
7 changed files with 426 additions and 129 deletions

View File

@ -62,9 +62,7 @@ class block_completionstatus extends block_base {
}
// Check this user is enroled
$users = $info->internal_get_tracked_users(true);
if (!in_array($USER->id, array_keys($users))) {
if (!$info->is_tracked_user($USER->id)) {
// If not enrolled, but are can view the report:
if (has_capability('coursereport/completion:view', get_context_instance(CONTEXT_COURSE, $COURSE->id))) {
$this->content->text = '<a href="'.$CFG->wwwroot.'/course/report/completion/index.php?course='.$COURSE->id.

View File

@ -93,8 +93,7 @@ if (empty($completions)) {
}
// Check this user is enroled
$users = $info->internal_get_tracked_users(true);
if (!in_array($user->id, array_keys($users))) {
if (!$info->is_tracked_user($user->id)) {
error(get_string('notenroled', 'completion'));
}

View File

@ -72,8 +72,7 @@ class block_selfcompletion extends block_base {
}
// Check this user is enroled
$users = $info->internal_get_tracked_users(true);
if (!in_array($USER->id, array_keys($users))) {
if (!$info->is_tracked_user($USER->id)) {
$this->content->text = get_string('notenroled', 'completion');
return $this->content;
}

View File

@ -54,8 +54,10 @@ $firstnamesort = ($sort == 'firstname');
$excel = ($format == 'excelcsv');
$csv = ($format == 'csv' || $excel);
// Whether to start at a particular position
$start = optional_param('start',0,PARAM_INT);
// Paging
$start = optional_param('start', 0, PARAM_INT);
$sifirst = optional_param('sifirst', 'all', PARAM_ALPHA);
$silast = optional_param('silast', 'all', PARAM_ALPHA);
// Whether to show idnumber
$idnumbers = $CFG->grade_report_showuseridnumber;
@ -137,13 +139,6 @@ if (!$csv) {
}
}
// Get user data
$progress = $completion->get_progress_all(
$firstnamesort, $group,
$csv ? 0 : COMPLETION_REPORT_PAGE,
$csv ? 0 : $start);
/**
* Setup page header
*/
@ -168,7 +163,7 @@ if ($csv) {
$PAGE->set_title($strcompletion);
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
$PAGE->requires->yui2_lib(
@ -183,36 +178,122 @@ if ($csv) {
$PAGE->requires->js('/course/report/completion/textrotate.js');
// Handle groups (if enabled)
groups_print_course_menu($course, $CFG->wwwroot.'/course/report/progress/?course='.$course->id);
groups_print_course_menu($course, $CFG->wwwroot.'/course/report/completion/?course='.$course->id);
}
// Generate where clause
$where = array();
$ilike = $DB->sql_ilike();
if ($sifirst !== 'all') {
$where[] = "u.firstname $ilike '$sifirst%'";
}
if ($silast !== 'all') {
$where[] = "u.lastname $ilike '$silast%'";
}
// Get user match count
$total = $completion->get_num_tracked_users(implode(' AND ', $where), $group);
// Total user count
$grandtotal = $completion->get_num_tracked_users('', $group);
// If no users in this course what-so-ever
if (!$grandtotal) {
echo $OUTPUT->container(get_string('err_nousers', 'completion'), 'errorbox errorboxcontent');
echo $OUTPUT->footer();
exit;
}
// Get user data
$progress = array();
if ($total) {
$progress = $completion->get_progress_all(
implode(' AND ', $where),
$group,
$firstnamesort ? 'u.firstname ASC' : 'u.lastname ASC',
$csv ? 0 : COMPLETION_REPORT_PAGE,
$csv ? 0 : $start
);
}
// Build link for paging
$link = $CFG->wwwroot.'/course/report/completion/?course='.$course->id;
if (strlen($sort)) {
$link .= '&amp;sort='.$sort;
}
$link .= '&amp;start=';
// Build the the page by Initial bar
$initials = array('first', 'last');
$alphabet = explode(',', get_string('alphabet', 'langconfig'));
$pagingbar = '';
foreach ($initials as $initial) {
$var = 'si'.$initial;
$pagingbar .= ' <div class="initialbar '.$initial.'initial">';
$pagingbar .= get_string($initial.'name').':&nbsp;';
if ($$var == 'all') {
$pagingbar .= '<strong>'.get_string('all').'</strong> ';
}
else {
$pagingbar .= '<a href="'.$link.'">'.get_string('all').'</a> ';
}
foreach ($alphabet as $letter) {
if ($$var === $letter) {
$pagingbar .= '<strong>'.$letter.'</strong> ';
}
else {
$pagingbar .= '<a href="'.$link.'&amp;'.$var.'='.$letter.'">'.$letter.'</a> ';
}
}
$pagingbar .= '</div>';
}
// Do we need a paging bar?
if($progress->total > COMPLETION_REPORT_PAGE) {
$pagingbar='<div class="completion_pagingbar">';
if($total > COMPLETION_REPORT_PAGE) {
if($start>0) {
$newstart=$start-COMPLETION_REPORT_PAGE;
if($newstart<0) {
$newstart=0;
// Paging bar
$pagingbar .= '<div class="paging">';
$pagingbar .= get_string('page').': ';
// Display previous link
if ($start > 0) {
$pstart = max($start - COMPLETION_REPORT_PAGE, 0);
$pagingbar .= '(<a class="previous" href="'.$link.$pstart.'">'.get_string('previous').'</a>)&nbsp;';
}
// Create page links
$curstart = 0;
$curpage = 0;
while ($curstart < $total) {
$curpage++;
if ($curstart == $start) {
$pagingbar .= '&nbsp;'.$curpage.'&nbsp;';
}
$pagingbar.=link_arrow_left(get_string('previous'),'./?course='.$course->id.
($newstart ? '&amp;start='.$newstart : ''),false,'completion_prev');
else {
$pagingbar .= '&nbsp;<a href="'.$link.$curstart.'">'.$curpage.'</a>&nbsp;';
}
$curstart += COMPLETION_REPORT_PAGE;
}
$a=new StdClass;
$a->from=$start+1;
$a->to=$start+COMPLETION_REPORT_PAGE;
$a->total=$progress->total;
$pagingbar.='<p>'.get_string('reportpage','completion',$a).'</p>';
if($start+COMPLETION_REPORT_PAGE < $progress->total) {
$pagingbar.=link_arrow_right(get_string('next'),'./?course='.$course->id.
'&amp;start='.($start+COMPLETION_REPORT_PAGE),false,'completion_next');
// Display next link
$nstart = $start + COMPLETION_REPORT_PAGE;
if ($nstart < $total) {
$pagingbar .= '&nbsp;(<a class="next" href="'.$link.$nstart.'">'.get_string('next').'</a>)';
}
$pagingbar.='</div>';
} else {
$pagingbar='';
$pagingbar .= '</div>';
}
@ -224,16 +305,17 @@ if($progress->total > COMPLETION_REPORT_PAGE) {
if(!$csv) {
print '<br class="clearer"/>'; // ugh
if(count($progress->users)==0) {
echo $OUTPUT->box_start('errorbox errorboxcontent boxaligncenter boxwidthnormal');
print '<p class="nousers">'.get_string('err_nousers','completion').'</p>';
print '<p><a href="'.$CFG->wwwroot.'/course/report.php?id='.$course->id.'">'.get_string('continue').'</a></p>';
echo $OUTPUT->box_end();
echo $OUTPUT->footer($course);
$total_header = ($total == $grandtotal) ? $total : "{$total}/{$grandtotal}";
echo $OUTPUT->heading(get_string('allparticipants').": {$total_header}", 3);
print $pagingbar;
if (!$total) {
echo $OUTPUT->heading(get_string('nothingtodisplay'), 2);
echo $OUTPUT->footer();
exit;
}
print $pagingbar;
print '<table id="completion-progress" class="generaltable flexible boxaligncenter completionreport" style="text-align: left" cellpadding="5" border="1">';
// Print criteria group names
@ -447,7 +529,7 @@ if(!$csv) {
///
/// Display a row for each user
///
foreach($progress->users as $user) {
foreach ($progress as $user) {
// User name
if($csv) {

View File

@ -2,7 +2,7 @@
require_once('../../../config.php');
require_once($CFG->libdir . '/completionlib.php');
define('COMPLETION_REPORT_PAGE',50);
define('COMPLETION_REPORT_PAGE', 25);
// Get course
$id = required_param('course',PARAM_INT);
@ -12,22 +12,25 @@ if(!$course) {
}
// Sort (default lastname, optionally firstname)
$sort=optional_param('sort','',PARAM_ALPHA);
$firstnamesort=$sort=='firstname';
$sort = optional_param('sort','',PARAM_ALPHA);
$firstnamesort = $sort == 'firstname';
// CSV format
$format=optional_param('format','',PARAM_ALPHA);
$excel=$format=='excelcsv';
$csv=$format=='csv' || $excel;
$format = optional_param('format','',PARAM_ALPHA);
$excel = $format == 'excelcsv';
$csv = $format == 'csv' || $excel;
// Whether to start at a particular position
$start=optional_param('start',0,PARAM_INT);
// Paging
$start = optional_param('start', 0, PARAM_INT);
$sifirst = optional_param('sifirst', 'all', PARAM_ALPHA);
$silast = optional_param('silast', 'all', PARAM_ALPHA);
$start = optional_param('start',0,PARAM_INT);
// Whether to show idnumber
// TODO: This should really not be using a config option 'intended' for
// gradebook, but that option is also used in quiz reports as well. There ought
// to be a generic option somewhere.
$idnumbers=$CFG->grade_report_showuseridnumber;
$idnumbers = $CFG->grade_report_showuseridnumber;
function csv_quote($value) {
global $excel;
@ -73,8 +76,46 @@ if(count($activities)==0) {
print_error('err_noactivities','completion',$reportsurl);
}
$progress=$completion->get_progress_all($firstnamesort,$group,
$csv ? 0 :COMPLETION_REPORT_PAGE,$csv ? 0 : $start);
// Generate where clause
$where = array();
$ilike = $DB->sql_ilike();
if ($sifirst !== 'all') {
$where[] = "u.firstname $ilike '$sifirst%'";
}
if ($silast !== 'all') {
$where[] = "u.lastname $ilike '$silast%'";
}
// Get user match count
$total = $completion->get_num_tracked_users(implode(' AND ', $where), $group);
// Total user count
$grandtotal = $completion->get_num_tracked_users('', $group);
// If no users in this course what-so-ever
if (!$grandtotal) {
print_box_start('errorbox errorboxcontent boxaligncenter boxwidthnormal');
print '<p class="nousers">'.get_string('err_nousers','completion').'</p>';
print '<p><a href="'.$CFG->wwwroot.'/course/report.php?id='.$course->id.'">'.get_string('continue').'</a></p>';
print_box_end();
print_footer($course);
exit;
}
// Get user data
$progress = array();
if ($total) {
$progress = $completion->get_progress_all(
implode(' AND ', $where),
$group,
$firstnamesort ? 'u.firstname ASC' : 'u.lastname ASC',
$csv ? 0 : COMPLETION_REPORT_PAGE,
$csv ? 0 : $start
);
}
if($csv) {
header('Content-Disposition: attachment; filename=progress.'.
@ -111,33 +152,79 @@ if($csv) {
groups_print_course_menu($course,$CFG->wwwroot.'/course/report/progress/?course='.$course->id);
}
// Do we need a paging bar?
if($progress->total > COMPLETION_REPORT_PAGE) {
$pagingbar='<div class="completion_pagingbar">';
// Build link for paging
$link = $CFG->wwwroot.'/course/report/progress/?course='.$course->id;
if (strlen($sort)) {
$link .= '&amp;sort='.$sort;
}
$link .= '&amp;start=';
if($start>0) {
$newstart=$start-COMPLETION_REPORT_PAGE;
if($newstart<0) {
$newstart=0;
// Build the the page by Initial bar
$initials = array('first', 'last');
$alphabet = explode(',', get_string('alphabet', 'langconfig'));
$pagingbar = '';
foreach ($initials as $initial) {
$var = 'si'.$initial;
$pagingbar .= ' <div class="initialbar '.$initial.'initial">';
$pagingbar .= get_string($initial.'name').':&nbsp;';
if ($$var == 'all') {
$pagingbar .= '<strong>'.get_string('all').'</strong> ';
}
else {
$pagingbar .= '<a href="'.$link.'">'.get_string('all').'</a> ';
}
foreach ($alphabet as $letter) {
if ($$var === $letter) {
$pagingbar .= '<strong>'.$letter.'</strong> ';
}
else {
$pagingbar .= '<a href="'.$link.'&amp;'.$var.'='.$letter.'">'.$letter.'</a> ';
}
$pagingbar.=link_arrow_left(get_string('previous'),'./?course='.$course->id.
($newstart ? '&amp;start='.$newstart : ''),false,'completion_prev');
}
$a=new StdClass;
$a->from=$start+1;
$a->to=$start+COMPLETION_REPORT_PAGE;
$a->total=$progress->total;
$pagingbar.='<p>'.get_string('reportpage','completion',$a).'</p>';
$pagingbar .= '</div>';
}
if($start+COMPLETION_REPORT_PAGE < $progress->total) {
$pagingbar.=link_arrow_right(get_string('next'),'./?course='.$course->id.
'&amp;start='.($start+COMPLETION_REPORT_PAGE),false,'completion_next');
// Do we need a paging bar?
if($total > COMPLETION_REPORT_PAGE) {
// Paging bar
$pagingbar .= '<div class="paging">';
$pagingbar .= get_string('page').': ';
// Display previous link
if ($start > 0) {
$pstart = max($start - COMPLETION_REPORT_PAGE, 0);
$pagingbar .= '(<a class="previous" href="'.$link.$pstart.'">'.get_string('previous').'</a>)&nbsp;';
}
$pagingbar.='</div>';
} else {
$pagingbar='';
// Create page links
$curstart = 0;
$curpage = 0;
while ($curstart < $total) {
$curpage++;
if ($curstart == $start) {
$pagingbar .= '&nbsp;'.$curpage.'&nbsp;';
}
else {
$pagingbar .= '&nbsp;<a href="'.$link.$curstart.'">'.$curpage.'</a>&nbsp;';
}
$curstart += COMPLETION_REPORT_PAGE;
}
// Display next link
$nstart = $start + COMPLETION_REPORT_PAGE;
if ($nstart < $total) {
$pagingbar .= '&nbsp;(<a class="next" href="'.$link.$nstart.'">'.get_string('next').'</a>)';
}
$pagingbar .= '</div>';
}
// Okay, let's draw the table of progress info,
@ -145,13 +232,15 @@ if($progress->total > COMPLETION_REPORT_PAGE) {
// Start of table
if(!$csv) {
print '<br class="clearer"/>'; // ugh
if(count($progress->users)==0) {
print '<p class="nousers">'.get_string('err_nousers','completion').'</p>';
print '<p><a href="'.$reportsurl.'">'.get_string('continue').'</a></p>';
echo $OUTPUT->footer();
print $pagingbar;
if (!$total) {
print_heading(get_string('nothingtodisplay'));
print_footer($course);
exit;
}
print $pagingbar;
print '<table id="completion-progress" class="generaltable flexible boxaligncenter" style="text-align:left"><tr style="vertical-align:top">';
// User heading / sort option
@ -214,7 +303,7 @@ if($csv) {
}
// Row for each user
foreach($progress->users as $user) {
foreach($progress as $user) {
// User name
if($csv) {
print csv_quote(fullname($user));

View File

@ -57,6 +57,13 @@ function completion_cron_mark_started() {
mtrace('Marking users as started');
}
if (!empty($CFG->progresstrackedroles)) {
$roles = ' AND ra.roleid IN ('.$CFG->progresstrackedroles.')';
} else {
// This causes it to default to everyone (if there is no student role)
$roles = '';
}
/**
* A quick explaination of this horrible looking query
*
@ -77,8 +84,8 @@ function completion_cron_mark_started() {
c.id AS course,
u.id AS userid,
crc.id AS completionid,
ue.timestart,
ue.timecreated AS timeenrolled
ue.timestart AS timeenrolled,
ue.timecreated
FROM
{user} u
INNER JOIN
@ -90,6 +97,9 @@ function completion_cron_mark_started() {
INNER JOIN
{course} c
ON c.id = e.courseid
INNER JOIN
{role_assignments} ra
ON ra.userid = u.id
LEFT JOIN
{course_completions} crc
ON crc.course = c.id
@ -102,8 +112,7 @@ function completion_cron_mark_started() {
AND u.deleted = 0
AND ue.timestart < ?
AND (ue.timeend > ? OR ue.timeend = 0)
AND ue.timecreated < ?
AND (ue.timecreated > ? OR ue.timecreated = 0)
$roles
ORDER BY
course,
userid
@ -140,7 +149,7 @@ function completion_cron_mark_started() {
else {
// Not all enrol plugins fill out timestart correctly, so use whichever
// is non-zero
$current->timeenrolled = max($current->timestart, $current->timeenrolled);
$current->timeenrolled = max($current->timecreated, $current->timeenrolled);
}
// If we are at the last record,
@ -154,6 +163,7 @@ function completion_cron_mark_started() {
$completion->course = $prev->course;
$completion->timeenrolled = (string) $prev->timeenrolled;
$completion->timestarted = 0;
$completion->reaggregate = time();
if ($prev->completionid) {
$completion->id = $prev->completionid;
@ -232,7 +242,7 @@ function completion_cron_completions() {
SELECT DISTINCT
c.id AS course,
cr.id AS criteriaid,
cc.userid AS userid,
crc.userid AS userid,
cr.criteriatype AS criteriatype,
cc.timecompleted AS timecompleted
FROM
@ -251,13 +261,14 @@ function completion_cron_completions() {
c.enablecompletion = 1
AND crc.timecompleted IS NULL
AND crc.reaggregate > 0
AND crc.reaggregate < :timestarted
ORDER BY
course,
userid
';
// Check if result is empty
if (!$rs = $DB->get_recordset_sql($sql)) {
if (!$rs = $DB->get_recordset_sql($sql, array('timestarted' => $timestarted))) {
return;
}

View File

@ -758,7 +758,7 @@ class completion_info {
$this->delete_all_state($cm);
// Merge this with list of planned users (according to roles)
$trackedusers = $this->internal_get_tracked_users(false);
$trackedusers = $this->get_tracked_users();
foreach ($trackedusers as $trackeduser) {
$keepusers[] = $trackeduser->id;
}
@ -962,31 +962,154 @@ class completion_info {
return $result;
}
/**
* Gets list of users in a course whose progress is tracked for display on the
* progress report.
* Checks to see if the userid supplied has a tracked role in
* this course
*
* @global object
* @global object
* @uses CONTEXT_COURSE
* @param bool $sortfirstname Optional True to sort with firstname
* @param int $groupid Optionally restrict to groupid
* @return array Array of user objects containing id, firstname, lastname (empty if none)
* @param $userid User id
* @return bool
*/
function internal_get_tracked_users($sortfirstname = false, $groupid = 0) {
global $CFG, $DB;
function is_tracked_user($userid) {
global $DB;
$sql = "SELECT u.id ";
$sql .= $this->generate_tracked_user_sql();
$sql .= ' AND u.id = :user';
return $DB->record_exists_sql($sql, array('user' => (int)$userid));
}
/**
* Return number of users whose progress is tracked in this course
*
* Optionally supply a search's where clause, or a group id
*
* @param string $where Where clause
* @param int $groupid Group id
* @return int
*/
function get_num_tracked_users($where = '', $groupid = 0) {
global $DB;
$sql = "SELECT COUNT(u.id) ";
$sql .= $this->generate_tracked_user_sql($groupid);
if ($where) {
$sql .= " AND $where";
}
return $DB->count_records_sql($sql);
}
/**
* Return array of users whose progress is tracked in this course
*
* Optionally supply a search's where caluse, group id, sorting, paging
*
* @param string $where Where clause (optional)
* @param integer $groupid Group ID to restrict to (optional)
* @param string $sort Order by clause (optional)
* @param integer $limitfrom Result start (optional)
* @param integer $limitnum Result max size (optional)
* @return array
*/
function get_tracked_users($where = '', $groupid = 0, $sort = '',
$limitfrom = '', $limitnum = '') {
global $DB;
$sql = "
SELECT
u.id,
u.firstname,
u.lastname,
u.idnumber
";
$sql .= $this->generate_tracked_user_sql($groupid);
if ($where) {
$sql .= " AND $where";
}
if ($sort) {
$sql .= " ORDER BY $sort";
}
$users = $DB->get_records_sql($sql, null, $limitfrom, $limitnum);
return $users ? $users : array(); // In case it returns false
}
/**
* Generate the SQL for finding tracked users in this course
*
* Optionally supply a group id
*
* @param integer $groupid
* @return string
*/
function generate_tracked_user_sql($groupid = 0) {
global $CFG;
if (!empty($CFG->progresstrackedroles)) {
$roles = explode(', ', $CFG->progresstrackedroles);
$roles = ' AND ra.roleid IN ('.$CFG->progresstrackedroles.')';
} else {
// This causes it to default to everyone (if there is no student role)
$roles = array();
$roles = '';
}
$users = get_role_users($roles, get_context_instance(CONTEXT_COURSE, $this->course->id), true,
'u.id, u.firstname, u.lastname, u.idnumber',
$sortfirstname ? 'u.firstname ASC' : 'u.lastname ASC', true, $groupid);
$users = $users ? $users : array(); // In case it returns false
return $users;
// Build context sql
$context = get_context_instance(CONTEXT_COURSE, $this->course->id);
$parentcontexts = substr($context->path, 1); // kill leading slash
$parentcontexts = str_replace('/', ',', $parentcontexts);
if ($parentcontexts !== '') {
$parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
}
$groupjoin = '';
$groupselect = '';
if ($groupid) {
$groupjoin = "JOIN {groups_members} gm
ON gm.userid = u.id";
$groupselect = " AND gm.groupid = $groupid ";
}
$now = time();
$sql = "
FROM
{user} u
INNER JOIN
{role_assignments} ra
ON ra.userid = u.id
INNER JOIN
{role} r
ON r.id = ra.roleid
INNER JOIN
{user_enrolments} ue
ON ue.userid = u.id
INNER JOIN
{enrol} e
ON e.id = ue.enrolid
INNER JOIN
{course} c
ON c.id = e.courseid
$groupjoin
WHERE
(ra.contextid = {$context->id} $parentcontexts)
AND c.id = {$this->course->id}
AND ue.status = 0
AND e.status = 0
AND ue.timestart < $now
AND (ue.timeend > $now OR ue.timeend = 0)
$groupselect
$roles
";
return $sql;
}
/**
@ -1004,31 +1127,26 @@ class completion_info {
* @param bool $sortfirstname If true, sort by first name, otherwise sort by
* last name
* @param int $groupid Group ID or 0 (default)/false for all groups
* @param int $pagesize Number of users to actually return (0 = unlimited)
* @param int $start User to start at if paging (0 = first set)
* @param int $pagesize Number of users to actually return (optional)
* @param int $start User to start at if paging (optional)
* @return Object with ->total and ->start (same as $start) and ->users;
* an array of user objects (like mdl_user id, firstname, lastname)
* containing an additional ->progress array of coursemoduleid => completionstate
*/
public function get_progress_all($sortfirstname=false, $groupid=0,
$pagesize=0,$start=0) {
public function get_progress_all($where = '', $groupid = 0, $sort = '', $pagesize = '', $start = '') {
global $CFG, $DB;
$resultobject=new StdClass;
// Get list of applicable users
$users = $this->internal_get_tracked_users($sortfirstname, $groupid);
$resultobject->start=$start;
$resultobject->total=count($users);
$users=array_slice($users,$start,$pagesize==0 ? count($users)-$start : $pagesize);
$users = $this->get_tracked_users($where, $groupid, $sort, $start, $pagesize);
// Get progress information for these users in groups of 1, 000 (if needed)
// to avoid making the SQL IN too long
$resultobject->users=array();
$results = array();
$userids = array();
foreach ($users as $user) {
$userids[] = $user->id;
$resultobject->users[$user->id]=$user;
$resultobject->users[$user->id]->progress=array();
$results[$user->id] = $user;
$results[$user->id]->progress = array();
}
for($i=0; $i<count($userids); $i+=1000) {
@ -1037,21 +1155,22 @@ class completion_info {
list($insql, $params) = $DB->get_in_or_equal(array_slice($userids, $i, $blocksize));
array_splice($params, 0, 0, array($this->course->id));
$rs = $DB->get_recordset_sql("
SELECT
cmc.*
FROM
{course_modules} cm
INNER JOIN {course_modules_completion} cmc ON cm.id=cmc.coursemoduleid
WHERE
cm.course=? AND cmc.userid $insql
SELECT
cmc.*
FROM
{course_modules} cm
INNER JOIN {course_modules_completion} cmc ON cm.id=cmc.coursemoduleid
WHERE
cm.course=? AND cmc.userid $insql
", $params);
foreach ($rs as $progress) {
$resultobject->users[$progress->userid]->progress[$progress->coursemoduleid]=$progress;
$progress = (object)$progress;
$results[$progress->userid]->progress[$progress->coursemoduleid] = $progress;
}
$rs->close();
}
return $resultobject;
return $results;
}
/**