completion: MDL-2631 Improve queries, reports and fix problems checking for tracked...
authorAaron Barnes <aaronb@catalyst.net.nz>
Thu, 12 Aug 2010 03:40:35 +0000 (03:40 +0000)
committerAaron Barnes <aaronb@catalyst.net.nz>
Thu, 12 Aug 2010 03:40:35 +0000 (03:40 +0000)
blocks/completionstatus/block_completionstatus.php
blocks/completionstatus/details.php
blocks/selfcompletion/block_selfcompletion.php
course/report/completion/index.php
course/report/progress/index.php
lib/completion/cron.php
lib/completionlib.php

index 083debf..822a072 100644 (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.
index 6e8253d..7e98982 100644 (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'));
 }
 
index 50567bf..f08eef5 100644 (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;
         }
index 387f2d1..edce125 100644 (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);
 }
 
-// Do we need a paging bar?
-if($progress->total > COMPLETION_REPORT_PAGE) {
-    $pagingbar='<div class="completion_pagingbar">';
 
-    if($start>0) {
-        $newstart=$start-COMPLETION_REPORT_PAGE;
-        if($newstart<0) {
-            $newstart=0;
+// 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.=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>';
+}
+
+// Do we need a paging bar?
+if($total > COMPLETION_REPORT_PAGE) {
 
-    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');
+    // 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>';
 }
 
 
@@ -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) {
index c36deb2..128ab90 100644 (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=';
+
+// 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> ';
+    }
 
-    if($start>0) {
-        $newstart=$start-COMPLETION_REPORT_PAGE;
-        if($newstart<0) {
-            $newstart=0;
+    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>';
+}
+
+// Do we need a paging bar?
+if($total > COMPLETION_REPORT_PAGE) {
+
+    // Paging bar
+    $pagingbar .= '<div class="paging">';
+    $pagingbar .= get_string('page').': ';
 
-    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 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));
index c50477f..0a5ee7f 100644 (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;
     }
 
index b7972c6..c12622e 100644 (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 = '';
+        }
+
+        // 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.' )';
         }
-        $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;
+
+        $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;
     }
 
     /**