diff --git a/user/classes/participants_table.php b/user/classes/participants_table.php new file mode 100644 index 00000000000..1009f9d53c5 --- /dev/null +++ b/user/classes/participants_table.php @@ -0,0 +1,276 @@ +. + +/** + * Contains the class used for the displaying the participants table. + * + * @package core_user + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_user; + +defined('MOODLE_INTERNAL') || die; + +global $CFG; + +require_once($CFG->libdir . '/tablelib.php'); +require_once($CFG->dirroot . '/user/lib.php'); + +/** + * Class for the displaying the participants table. + * + * @package core_user + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class participants_table extends \table_sql { + + /** + * @var int $courseid The course id + */ + protected $courseid; + + /** + * @var int|false False if groups not used, int if groups used, 0 for all groups. + */ + protected $currentgroup; + + /** + * @var int $accesssince The time the user last accessed the site + */ + protected $accesssince; + + /** + * @var int $roleid The role we are including, 0 means all enrolled users + */ + protected $roleid; + + /** + * @var string $search The string being searched. + */ + protected $search; + + /** + * @var bool $selectall Has the user selected all users on the page? + */ + protected $selectall; + + /** + * @var string[] The list of countries. + */ + protected $countries; + + /** + * @var string[] Extra fields to display. + */ + protected $extrafields; + + /** + * Sets up the table. + * + * @param int $courseid + * @param int|false $currentgroup False if groups not used, int if groups used, 0 for all groups. + * @param int $accesssince The time the user last accessed the site + * @param int $roleid The role we are including, 0 means all enrolled users + * @param string $search The string being searched + * @param bool $bulkoperations Is the user allowed to perform bulk operations? + * @param bool $selectall Has the user selected all users on the page? + */ + public function __construct($courseid, $currentgroup, $accesssince, $roleid, $search, + $bulkoperations, $selectall) { + global $CFG; + + parent::__construct('user-index-participants-' . $courseid); + + // Get the context. + $context = \context_course::instance($courseid, MUST_EXIST); + + // Define the headers and columns. + $headers = []; + $columns = []; + + if ($bulkoperations) { + $headers[] = get_string('select'); + $columns[] = 'select'; + } + + $headers[] = get_string('fullname'); + $columns[] = 'fullname'; + + $extrafields = get_extra_user_fields($context); + foreach ($extrafields as $field) { + $headers[] = get_user_field_name($field); + $columns[] = $field; + } + + // Get the list of fields we have to hide. + $hiddenfields = array(); + if (!has_capability('moodle/course:viewhiddenuserfields', $context)) { + $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields)); + } + + // Do not show the columns if it exists in the hiddenfields array. + if (!isset($hiddenfields['city'])) { + $headers[] = get_string('city'); + $columns[] = 'city'; + } + if (!isset($hiddenfields['country'])) { + $headers[] = get_string('country'); + $columns[] = 'country'; + } + if (!isset($hiddenfields['lastaccess'])) { + if ($courseid == SITEID) { + $headers[] = get_string('lastsiteaccess'); + } else { + $headers[] = get_string('lastcourseaccess'); + } + $columns[] = 'lastaccess'; + } + + $this->define_columns($columns); + $this->define_headers($headers); + + $this->no_sorting('select'); + + $this->set_attribute('id', 'participants'); + + // Set the variables we need to use later. + $this->courseid = $courseid; + $this->currentgroup = $currentgroup; + $this->accesssince = $accesssince; + $this->roleid = $roleid; + $this->search = $search; + $this->selectall = $selectall; + $this->countries = get_string_manager()->get_list_of_countries(); + $this->extrafields = $extrafields; + } + + /** + * Generate the select column. + * + * @param \stdClass $data + * @return string + */ + public function col_select($data) { + if ($this->selectall) { + $checked = 'checked="true"'; + } else { + $checked = ''; + } + return ''; + } + + /** + * Generate the fullname column. + * + * @param \stdClass $data + * @return string + */ + public function col_fullname($data) { + global $OUTPUT; + + return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->courseid)) . ' ' . fullname($data); + } + + /** + * Generate the city column. + * + * @param \stdClass $data + * @return string + */ + public function col_city($data) { + return $data->city; + } + + /** + * Generate the country column. + * + * @param \stdClass $data + * @return string + */ + public function col_country($data) { + if (!empty($this->countries[$data->country])) { + return $this->countries[$data->country]; + } + return ''; + } + + /** + * Generate the last access column. + * + * @param \stdClass $data + * @return string + */ + public function col_lastaccess($data) { + if ($data->lastaccess) { + return format_time(time() - $data->lastaccess); + } + + return get_string('never'); + } + + /** + * This function is used for the extra user fields. + * + * These are being dynamically added to the table so there are no functions 'col_' as + * the list has the potential to increase in the future and we don't want to have to remember to add + * a new method to this class. We also don't want to pollute this class with unnecessary methods. + * + * @param string $colname The column name + * @param \stdClass $data + * @return string + */ + public function other_cols($colname, $data) { + // Do not process if it is not a part of the extra fields. + if (!in_array($colname, $this->extrafields)) { + return ''; + } + + return s($data->{$colname}); + } + + /** + * Query the database for results to display in the table. + * + * @param int $pagesize size of page for paginated displayed table. + * @param bool $useinitialsbar do you want to use the initials bar. + */ + public function query_db($pagesize, $useinitialsbar = true) { + list($twhere, $tparams) = $this->get_sql_where(); + + $total = user_get_total_participants($this->courseid, $this->currentgroup, $this->accesssince, + $this->roleid, $this->search, $twhere, $tparams); + + $this->pagesize($pagesize, $total); + + $sort = $this->get_sql_sort(); + if ($sort) { + $sort = 'ORDER BY ' . $sort; + } + + $this->rawdata = user_get_participants($this->courseid, $this->currentgroup, $this->accesssince, + $this->roleid, $this->search, $twhere, $tparams, $sort, $this->get_page_start(), + $this->get_page_size()); + + // Set initial bars. + if ($useinitialsbar) { + $this->initialbars(true); + } + } +} + diff --git a/user/index.php b/user/index.php index e4c7c46a1bf..f2de5f2a479 100644 --- a/user/index.php +++ b/user/index.php @@ -27,8 +27,6 @@ require_once($CFG->dirroot.'/user/lib.php'); require_once($CFG->libdir.'/tablelib.php'); require_once($CFG->libdir.'/filelib.php'); -define('USER_SMALL_CLASS', 20); // Below this is considered small. -define('USER_LARGE_CLASS', 200); // Above this is considered large. define('DEFAULT_PAGE_SIZE', 20); define('SHOW_ALL_PAGE_SIZE', 5000); @@ -110,20 +108,6 @@ $bulkoperations = has_capability('moodle/course:bulkmessaging', $context); $countries = get_string_manager()->get_list_of_countries(); -$strnever = get_string('never'); - -$datestring = new stdClass(); -$datestring->year = get_string('year'); -$datestring->years = get_string('years'); -$datestring->day = get_string('day'); -$datestring->days = get_string('days'); -$datestring->hour = get_string('hour'); -$datestring->hours = get_string('hours'); -$datestring->min = get_string('min'); -$datestring->mins = get_string('mins'); -$datestring->sec = get_string('sec'); -$datestring->secs = get_string('secs'); - // Check to see if groups are being used in this course // and if so, set $currentgroup to reflect the current group. @@ -154,7 +138,6 @@ if ($isseparategroups and (!$currentgroup) ) { exit; } - // Should use this variable so that we don't break stuff every time a variable is added or changed. $baseurl = new moodle_url('/user/index.php', array( 'contextid' => $context->id, @@ -175,20 +158,6 @@ if ($course->id == SITEID) { $filterselect = $currentgroup; } - - -// Get the hidden field list. -if (has_capability('moodle/course:viewhiddenuserfields', $context)) { - $hiddenfields = array(); // Teachers and admins are allowed to see everything. -} else { - $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields)); -} - -if (isset($hiddenfields['lastaccess'])) { - // Do not allow access since filtering. - $accesssince = 0; -} - // Print settings and things in a table across the top. $controlstable = new html_table(); $controlstable->attributes['class'] = 'controls'; @@ -199,7 +168,16 @@ if ($groupmenu = groups_print_course_menu($course, $baseurl->out(), true)) { $controlstable->data[0]->cells[] = $groupmenu; } -if (!isset($hiddenfields['lastaccess'])) { +// Get the list of fields we have to hide. +$hiddenfields = array(); +if (!has_capability('moodle/course:viewhiddenuserfields', $context)) { + $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields)); +} + +// If lastaccess is in the hidden fields access do not allow filtering. +if (isset($hiddenfields['lastaccess'])) { + $accesssince = 0; +} else { // The user is allowed to filter by last access. // Get minimum lastaccess for this course and display a dropbox to filter by lastaccess going back this far. // We need to make it diferently for normal courses and site course. if (!$isfrontpage) { @@ -258,160 +236,15 @@ if (!isset($hiddenfields['lastaccess'])) { echo html_writer::table($controlstable); -// Define a table showing a list of users in the current role selection. -$tablecolumns = array(); -$tableheaders = array(); -if ($bulkoperations) { - $tablecolumns[] = 'select'; - $tableheaders[] = get_string('select'); -} -$tablecolumns[] = 'userpic'; -$tablecolumns[] = 'fullname'; +$participanttable = new \core_user\participants_table($course->id, $currentgroup, $accesssince, $roleid, $search, + $bulkoperations, $selectall); +$participanttable->define_baseurl($baseurl); -$extrafields = get_extra_user_fields($context); -$tableheaders[] = get_string('userpic'); -$tableheaders[] = get_string('fullnameuser'); - - -foreach ($extrafields as $field) { - $tablecolumns[] = $field; - $tableheaders[] = get_user_field_name($field); -} -if (!isset($hiddenfields['city'])) { - $tablecolumns[] = 'city'; - $tableheaders[] = get_string('city'); -} -if (!isset($hiddenfields['country'])) { - $tablecolumns[] = 'country'; - $tableheaders[] = get_string('country'); -} -if (!isset($hiddenfields['lastaccess'])) { - $tablecolumns[] = 'lastaccess'; - if ($course->id == SITEID) { - // Exception case for viewing participants on site home. - $tableheaders[] = get_string('lastsiteaccess'); - } else { - $tableheaders[] = get_string('lastcourseaccess'); - } -} - -$table = new flexible_table('user-index-participants-'.$course->id); -$table->define_columns($tablecolumns); -$table->define_headers($tableheaders); -$table->define_baseurl($baseurl->out()); - -if (!isset($hiddenfields['lastaccess'])) { - $table->sortable(true, 'lastaccess', SORT_DESC); -} else { - $table->sortable(true, 'firstname', SORT_ASC); -} - -$table->no_sorting('roles'); -$table->no_sorting('groups'); -$table->no_sorting('groupings'); -$table->no_sorting('select'); - -$table->set_attribute('cellspacing', '0'); -$table->set_attribute('id', 'participants'); -$table->set_attribute('class', 'generaltable generalbox'); - -$table->set_control_variables(array( - TABLE_VAR_SORT => 'ssort', - TABLE_VAR_HIDE => 'shide', - TABLE_VAR_SHOW => 'sshow', - TABLE_VAR_IFIRST => 'sifirst', - TABLE_VAR_ILAST => 'silast', - TABLE_VAR_PAGE => 'spage' - )); -$table->setup(); - -list($esql, $params) = get_enrolled_sql($context, null, $currentgroup, true); -$joins = array("FROM {user} u"); -$wheres = array(); - -$userfields = array('username', 'email', 'city', 'country', 'lang', 'timezone', 'maildisplay'); -$mainuserfields = user_picture::fields('u', $userfields); -$extrasql = get_extra_user_fields_sql($context, 'u', '', $userfields); - -if ($isfrontpage) { - $select = "SELECT $mainuserfields, u.lastaccess$extrasql"; - $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Everybody on the frontpage usually. - if ($accesssince) { - $wheres[] = get_user_lastaccess_sql($accesssince); - } - -} else { - $select = "SELECT $mainuserfields, COALESCE(ul.timeaccess, 0) AS lastaccess$extrasql"; - $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Course enrolled users only. - $joins[] = "LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid)"; // Not everybody accessed course yet. - $params['courseid'] = $course->id; - if ($accesssince) { - $wheres[] = get_course_lastaccess_sql($accesssince); - } -} - -// Performance hacks - we preload user contexts together with accounts. -$ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx'); -$ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)"; -$params['contextlevel'] = CONTEXT_USER; -$select .= $ccselect; -$joins[] = $ccjoin; - - -// Limit list to users with some role only. -if ($roleid) { - // We want to query both the current context and parent contexts. - list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx'); - - $wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)"; - $params = array_merge($params, array('roleid' => $roleid), $relatedctxparams); -} - -$from = implode("\n", $joins); -if ($wheres) { - $where = "WHERE " . implode(" AND ", $wheres); -} else { - $where = ""; -} - -$totalcount = $DB->count_records_sql("SELECT COUNT(u.id) $from $where", $params); - -if (!empty($search)) { - $fullname = $DB->sql_fullname('u.firstname', 'u.lastname'); - $wheres[] = "(". $DB->sql_like($fullname, ':search1', false, false) . - " OR ". $DB->sql_like('email', ':search2', false, false) . - " OR ". $DB->sql_like('idnumber', ':search3', false, false) .") "; - $params['search1'] = "%$search%"; - $params['search2'] = "%$search%"; - $params['search3'] = "%$search%"; -} - -list($twhere, $tparams) = $table->get_sql_where(); -if ($twhere) { - $wheres[] = $twhere; - $params = array_merge($params, $tparams); -} - -$from = implode("\n", $joins); -if ($wheres) { - $where = "WHERE " . implode(" AND ", $wheres); -} else { - $where = ""; -} - -if ($table->get_sql_sort()) { - $sort = ' ORDER BY '.$table->get_sql_sort(); -} else { - $sort = ''; -} - -$matchcount = $DB->count_records_sql("SELECT COUNT(u.id) $from $where", $params); - -$table->initialbars(true); -$table->pagesize($perpage, $matchcount); - -// List of users at the current visible page - paging makes it relatively short. -$userlist = $DB->get_recordset_sql("$select $from $where $sort", $params, $table->get_page_start(), $table->get_page_size()); +// Do this so we can get the total number of rows. +ob_start(); +$participanttable->out($perpage, true); +$participanttablehtml = ob_get_contents(); +ob_end_clean(); // If there are multiple Roles in the course, then show a drop down menu for switching. if (count($rolenames) > 1) { @@ -429,14 +262,9 @@ if (count($rolenames) > 1) { echo ''; } -$editlink = ''; -if ($course->id != SITEID && has_capability('moodle/course:enrolreview', $context)) { - $editlink = new moodle_url('/enrol/users.php', array('id' => $course->id)); -} - if ($roleid > 0) { $a = new stdClass(); - $a->number = $totalcount; + $a->number = $participanttable->totalrows; $a->role = $rolenames[$roleid]; $heading = format_string(get_string('xuserswiththerole', 'role', $a)); @@ -454,10 +282,6 @@ if ($roleid > 0) { $heading .= ": $a->number"; - if (!empty($editlink)) { - $editlink->param('role', $roleid); - $heading .= $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'))); - } echo $OUTPUT->heading($heading, 3); } else { if ($course->id == SITEID and $roleid < 0) { @@ -466,18 +290,9 @@ if ($roleid > 0) { $strallparticipants = get_string('allparticipants'); } - if (!empty($editlink)) { - $editlink = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'))); - } - - if ($matchcount < $totalcount) { - echo $OUTPUT->heading($strallparticipants.get_string('labelsep', 'langconfig').$matchcount.'/'.$totalcount . $editlink, 3); - } else { - echo $OUTPUT->heading($strallparticipants.get_string('labelsep', 'langconfig').$matchcount . $editlink, 3); - } + echo $OUTPUT->heading($strallparticipants.get_string('labelsep', 'langconfig') . $participanttable->totalrows, 3); } - if ($bulkoperations) { echo '
'; echo '
'; @@ -485,92 +300,24 @@ if ($bulkoperations) { echo ''; } -$countrysort = (strpos($sort, 'country') !== false); -$timeformat = get_string('strftimedate'); - -if ($userlist) { - - $usersprinted = array(); - foreach ($userlist as $user) { - if (in_array($user->id, $usersprinted)) { // Prevent duplicates by r.hidden - MDL-13935. - continue; - } - $usersprinted[] = $user->id; // Add new user to the array of users printed. - - context_helper::preload_from_record($user); - - if ($user->lastaccess) { - $lastaccess = format_time(time() - $user->lastaccess, $datestring); - } else { - $lastaccess = $strnever; - } - - if (empty($user->country)) { - $country = ''; - - } else { - if ($countrysort) { - $country = '('.$user->country.') '.$countries[$user->country]; - } else { - $country = $countries[$user->country]; - } - } - - $usercontext = context_user::instance($user->id); - - if ($piclink = ($USER->id == $user->id || has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext))) { - $profilelink = ''.fullname($user).''; - } else { - $profilelink = ''.fullname($user).''; - } - - $data = array(); - if ($bulkoperations) { - if ($selectall) { - $checked = 'checked="true"'; - } else { - $checked = ''; - } - $data[] = ''; - } - $data[] = $OUTPUT->user_picture($user, array('size' => 35, 'courseid' => $course->id)); - $data[] = $profilelink; - - foreach ($extrafields as $field) { - $data[] = $user->{$field}; - } - - if (!isset($hiddenfields['city'])) { - $data[] = $user->city; - } - if (!isset($hiddenfields['country'])) { - $data[] = $country; - } - if (!isset($hiddenfields['lastaccess'])) { - $data[] = $lastaccess; - } - - $table->add_data($data); - } -} - -$table->print_html(); +echo $participanttablehtml; $perpageurl = clone($baseurl); $perpageurl->remove_params('perpage'); -if ($perpage == SHOW_ALL_PAGE_SIZE) { +if ($perpage == SHOW_ALL_PAGE_SIZE && $participanttable->totalrows > DEFAULT_PAGE_SIZE) { $perpageurl->param('perpage', DEFAULT_PAGE_SIZE); echo $OUTPUT->container(html_writer::link($perpageurl, get_string('showperpage', '', DEFAULT_PAGE_SIZE)), array(), 'showall'); -} else if ($matchcount > 0 && $perpage < $matchcount) { +} else if ($participanttable->get_page_size() < $participanttable->totalrows) { $perpageurl->param('perpage', SHOW_ALL_PAGE_SIZE); - echo $OUTPUT->container(html_writer::link($perpageurl, get_string('showall', '', $matchcount)), array(), 'showall'); + echo $OUTPUT->container(html_writer::link($perpageurl, get_string('showall', '', $participanttable->totalrows)), + array(), 'showall'); } if ($bulkoperations) { echo '
'; - if ($matchcount > 0 && $perpage < $matchcount) { + if ($participanttable->get_page_size() < $participanttable->totalrows) { $perpageurl = clone($baseurl); $perpageurl->remove_params('perpage'); $perpageurl->param('perpage', SHOW_ALL_PAGE_SIZE); @@ -581,9 +328,9 @@ if ($bulkoperations) { } echo html_writer::start_tag('div', array('class' => 'btn-group')); - if ($perpage < $matchcount) { + if ($participanttable->get_page_size() < $participanttable->totalrows) { // Select all users, refresh page showing all users and mark them all selected. - $label = get_string('selectalluserswithcount', 'moodle', $matchcount); + $label = get_string('selectalluserswithcount', 'moodle', $participanttable->totalrows); echo html_writer::tag('input', "", array('type' => 'button', 'id' => 'checkall', 'class' => 'btn btn-secondary', 'value' => $label, 'data-showallink' => $showalllink)); // Select all users, mark all users on page as selected. @@ -620,7 +367,7 @@ if ($bulkoperations) { } // Show a search box if all participants don't fit on a single screen. -if ($totalcount > $perpage) { +if ($participanttable->get_page_size() < $participanttable->totalrows) { echo '
'; echo ''; echo ' 
'."\n"; @@ -629,41 +376,3 @@ if ($totalcount > $perpage) { echo '
'; // Userlist. echo $OUTPUT->footer(); - -if ($userlist) { - $userlist->close(); -} - -/** - * Returns SQL that can be used to limit a query to a period where the user last accessed a course.. - * - * @param string $accesssince - * @return string - */ -function get_course_lastaccess_sql($accesssince='') { - if (empty($accesssince)) { - return ''; - } - if ($accesssince == -1) { // Never. - return 'ul.timeaccess = 0'; - } else { - return 'ul.timeaccess != 0 AND ul.timeaccess < '.$accesssince; - } -} - -/** - * Returns SQL that can be used to limit a query to a period where the user last accessed the system. - * - * @param string $accesssince - * @return string - */ -function get_user_lastaccess_sql($accesssince='') { - if (empty($accesssince)) { - return ''; - } - if ($accesssince == -1) { // Never. - return 'u.lastaccess = 0'; - } else { - return 'u.lastaccess != 0 AND u.lastaccess < '.$accesssince; - } -} diff --git a/user/lib.php b/user/lib.php index 285a4a8787a..feccd711191 100644 --- a/user/lib.php +++ b/user/lib.php @@ -1212,3 +1212,177 @@ function user_get_tagged_users($tag, $exclusivemode = false, $fromctx = 0, $ctx return new core_tag\output\tagindex($tag, 'core', 'user', $content, $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages); } + +/** + * Returns the SQL used by the participants table. + * + * @param int $courseid The course id + * @param int $groupid The groupid, 0 means all groups + * @param int $accesssince The time since last access, 0 means any time + * @param int $roleid The role id, 0 means all roles + * @param string $search The search that was performed, empty means perform no search + * @param string $additionalwhere Any additional SQL to add to where + * @param array $additionalparams The additional params + * @return array + */ +function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $search = '', $additionalwhere = '', + $additionalparams = array()) { + global $DB; + + // Get the context. + $context = \context_course::instance($courseid, MUST_EXIST); + + $isfrontpage = ($courseid == SITEID); + + list($esql, $params) = get_enrolled_sql($context, null, $groupid, true); + + $joins = array('FROM {user} u'); + $wheres = array(); + + $userfields = array('username', 'email', 'city', 'country', 'lang', 'timezone', 'maildisplay'); + $mainuserfields = user_picture::fields('u', $userfields); + $extrasql = get_extra_user_fields_sql($context, 'u', '', $userfields); + + if ($isfrontpage) { + $select = "SELECT $mainuserfields, u.lastaccess$extrasql"; + $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Everybody on the frontpage usually. + if ($accesssince) { + $wheres[] = user_get_user_lastaccess_sql($accesssince); + } + } else { + $select = "SELECT $mainuserfields, COALESCE(ul.timeaccess, 0) AS lastaccess$extrasql"; + $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Course enrolled users only. + // Not everybody has accessed the course yet. + $joins[] = 'LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid)'; + $params['courseid'] = $courseid; + if ($accesssince) { + $wheres[] = user_get_course_lastaccess_sql($accesssince); + } + } + + // Performance hacks - we preload user contexts together with accounts. + $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx'); + $ccjoin = 'LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)'; + $params['contextlevel'] = CONTEXT_USER; + $select .= $ccselect; + $joins[] = $ccjoin; + + // Limit list to users with some role only. + if ($roleid) { + // We want to query both the current context and parent contexts. + list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), + SQL_PARAMS_NAMED, 'relatedctx'); + + $wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)"; + $params = array_merge($params, array('roleid' => $roleid), $relatedctxparams); + } + + if (!empty($search)) { + $fullname = $DB->sql_fullname('u.firstname', 'u.lastname'); + $wheres[] = '(' . $DB->sql_like($fullname, ':search1', false, false) . + ' OR ' . $DB->sql_like('email', ':search2', false, false) . + ' OR ' . $DB->sql_like('idnumber', ':search3', false, false) . ') '; + $params['search1'] = "%$search%"; + $params['search2'] = "%$search%"; + $params['search3'] = "%$search%"; + } + + if (!empty($additionalwhere)) { + $wheres[] = $additionalwhere; + $params = array_merge($params, $additionalparams); + } + + $from = implode("\n", $joins); + if ($wheres) { + $where = 'WHERE ' . implode(' AND ', $wheres); + } else { + $where = ''; + } + + return array($select, $from, $where, $params); +} + +/** + * Returns the total number of participants for a given course. + * + * @param int $courseid The course id + * @param int $groupid The groupid, 0 means all groups + * @param int $accesssince The time since last access, 0 means any time + * @param int $roleid The role id, 0 means all roles + * @param string $search The search that was performed, empty means perform no search + * @param string $additionalwhere Any additional SQL to add to where + * @param array $additionalparams The additional params + * @return int + */ +function user_get_total_participants($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $search = '', $additionalwhere = '', + $additionalparams = array()) { + global $DB; + + list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, + $search, $additionalwhere, $additionalparams); + + return $DB->count_records_sql("SELECT COUNT(u.id) $from $where", $params); +} + +/** + * Returns the participants for a given course. + * + * @param int $courseid The course id + * @param int $groupid The group id + * @param int $accesssince The time since last access + * @param int $roleid The role id + * @param string $search The search that was performed + * @param string $additionalwhere Any additional SQL to add to where + * @param array $additionalparams The additional params + * @param string $sort The SQL sort + * @param int $limitfrom return a subset of records, starting at this point (optional). + * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). + * @return moodle_recordset + */ +function user_get_participants($courseid, $groupid = 0, $accesssince, $roleid, $search, $additionalwhere = '', + $additionalparams = array(), $sort = '', $limitfrom = 0, $limitnum = 0) { + global $DB; + + list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, + $search, $additionalwhere, $additionalparams); + + return $DB->get_recordset_sql("$select $from $where $sort", $params, $limitfrom, $limitnum); +} + +/** + * Returns SQL that can be used to limit a query to a period where the user last accessed a course. + * + * @param int $accesssince The time since last access + * @param string $tableprefix + * @return string + */ +function user_get_course_lastaccess_sql($accesssince = null, $tableprefix = 'ul') { + if (empty($accesssince)) { + return ''; + } + + if ($accesssince == -1) { // Never. + return $tableprefix . '.timeaccess = 0'; + } else { + return $tableprefix . '.timeaccess != 0 AND ul.timeaccess < ' . $accesssince; + } +} + +/** + * Returns SQL that can be used to limit a query to a period where the user last accessed the system. + * + * @param int $accesssince The time since last access + * @param string $tableprefix + * @return string + */ +function user_get_user_lastaccess_sql($accesssince = null, $tableprefix = 'u') { + if (empty($accesssince)) { + return ''; + } + + if ($accesssince == -1) { // Never. + return $tableprefix . '.lastaccess = 0'; + } else { + return $tableprefix . '.lastaccess != 0 AND u.lastaccess < ' . $accesssince; + } +} diff --git a/user/tests/userlib_test.php b/user/tests/userlib_test.php index 73e86bbdd73..1edb2bad475 100644 --- a/user/tests/userlib_test.php +++ b/user/tests/userlib_test.php @@ -694,4 +694,183 @@ class core_userliblib_testcase extends advanced_testcase { self::assertSame('50', $got['timezone']); self::assertSame('0', $got['mailformat']); } + + /** + * Test returning the total number of participants. + */ + public function test_user_get_total_participants() { + global $DB; + + $this->resetAfterTest(); + + // Create a course. + $course = self::getDataGenerator()->create_course(); + + // Create a teacher. + $teacher = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + + // Create a bunch of students. + $student1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + $student2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + $student3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + + // Create a group. + $group = self::getDataGenerator()->create_group(array('courseid' => $course->id)); + + // Enrol the students. + self::getDataGenerator()->enrol_user($student1->id, $course->id); + self::getDataGenerator()->enrol_user($student2->id, $course->id); + self::getDataGenerator()->enrol_user($student3->id, $course->id); + + // Enrol the teacher. + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + self::getDataGenerator()->enrol_user($teacher->id, $course->id, $roleids['editingteacher']); + + // Add the teacher and two of the students to the group. + groups_add_member($group->id, $teacher->id); + groups_add_member($group->id, $student1->id); + groups_add_member($group->id, $student2->id); + + // Set it so the teacher and two of the students have accessed the courses within the last day, + // but only one of the students is in the group. + $accesssince = time() - DAYSECS; + $lastaccess = new stdClass(); + $lastaccess->userid = $teacher->id; + $lastaccess->courseid = $course->id; + $lastaccess->timeaccess = time() - DAYSECS; + $DB->insert_record('user_lastaccess', $lastaccess); + + $lastaccess->userid = $student1->id; + $DB->insert_record('user_lastaccess', $lastaccess); + + $lastaccess->userid = $student3->id; + $DB->insert_record('user_lastaccess', $lastaccess); + + // Now, when we perform the following search we should only return 1 user. A student who belongs to + // the group and has the name 'searchforthis' and has also accessed the course in the last day. + $count = user_get_total_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 'searchforthis'); + + $this->assertEquals(1, $count); + } + + /** + * Test returning the number of participants on the front page. + */ + public function test_user_get_total_participants_on_front_page() { + $this->resetAfterTest(); + + // Set it so that only 3 users have accessed the site within the last day. + $accesssince = time() - DAYSECS; + + // Create a bunch of users. + $user1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]); + $user2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]); + $user3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + $user4 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]); + + // Create a group. + $group = self::getDataGenerator()->create_group(array('courseid' => SITEID)); + + // Add 3 of the users to a group. + groups_add_member($group->id, $user1->id); + groups_add_member($group->id, $user2->id); + groups_add_member($group->id, $user3->id); + + // Now, when we perform the following search we should only return 2 users. Users who belong to + // the group and have the name 'searchforthis' and have also accessed the site in the last day. + $count = user_get_total_participants(SITEID, $group->id, $accesssince + 1, 0, 'searchforthis'); + + $this->assertEquals(2, $count); + } + + /** + * Test returning the participants. + */ + public function test_user_get_participants() { + global $DB; + + $this->resetAfterTest(); + + // Create a course. + $course = self::getDataGenerator()->create_course(); + + // Create a teacher. + $teacher = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + + // Create a bunch of students. + $student1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + $student2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + $student3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + + // Create a group. + $group = self::getDataGenerator()->create_group(array('courseid' => $course->id)); + + // Enrol the students. + self::getDataGenerator()->enrol_user($student1->id, $course->id); + self::getDataGenerator()->enrol_user($student2->id, $course->id); + self::getDataGenerator()->enrol_user($student3->id, $course->id); + + // Enrol the teacher. + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + self::getDataGenerator()->enrol_user($teacher->id, $course->id, $roleids['editingteacher']); + + // Add the teacher and two of the students to the group. + groups_add_member($group->id, $teacher->id); + groups_add_member($group->id, $student1->id); + groups_add_member($group->id, $student2->id); + + // Set it so the teacher and two of the students have accessed the course within the last day, but only one of + // the students is in the group. + $accesssince = time() - DAYSECS; + $lastaccess = new stdClass(); + $lastaccess->userid = $teacher->id; + $lastaccess->courseid = $course->id; + $lastaccess->timeaccess = time() - DAYSECS; + $DB->insert_record('user_lastaccess', $lastaccess); + + $lastaccess->userid = $student1->id; + $DB->insert_record('user_lastaccess', $lastaccess); + + $lastaccess->userid = $student3->id; + $DB->insert_record('user_lastaccess', $lastaccess); + + // Now, when we perform the following search we should only return 1 user. A student who belongs to + // the group and has the name 'searchforthis' and has also accessed the course in the last day. + $userset = user_get_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 'searchforthis'); + + $this->assertEquals(1, sizeof($userset)); + $this->assertEquals($student1->id, $userset->current()->id); + } + + /** + * Test returning the participants on the front page. + */ + public function test_user_get_participants_on_front_page() { + $this->resetAfterTest(); + + // Set it so that only 3 users have accessed the site within the last day. + $accesssince = time() - DAYSECS; + + // Create a bunch of users. + $user1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]); + $user2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]); + $user3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']); + $user4 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]); + + // Create a group. + $group = self::getDataGenerator()->create_group(array('courseid' => SITEID)); + + // Add 3 of the users to a group. + groups_add_member($group->id, $user1->id); + groups_add_member($group->id, $user2->id); + groups_add_member($group->id, $user3->id); + + // Now, when we perform the following search we should only return 2 users. Users who belong to + // the group and have the name 'searchforthis' and have also accessed the site in the last day. + $userset = user_get_participants(SITEID, $group->id, $accesssince + 1, 0, 'searchforthis'); + + $this->assertEquals($user1->id, $userset->current()->id); + $userset->next(); + $this->assertEquals($user2->id, $userset->current()->id); + } }