From bc47b706672dc38dbbf70ca3d3b2f0fb865c19bc Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Wed, 28 Jun 2017 11:44:20 +0800 Subject: [PATCH 1/2] MDL-59395 core_user: move participants table to own class Removed the edit link variable which was used in the heading 'Users with the role ..' but was never displayed due to filters. Other minor tidy ups as well. Part of MDL-59290. --- user/classes/participants_table.php | 276 ++++++++++++++++++++++ user/index.php | 349 +++------------------------- user/lib.php | 174 ++++++++++++++ 3 files changed, 479 insertions(+), 320 deletions(-) create mode 100644 user/classes/participants_table.php 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; + } +} From ba6645daa08d948a94a7cef5e0dd1a2347cef9d6 Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Mon, 3 Jul 2017 16:08:24 +0800 Subject: [PATCH 2/2] MDL-59395 core_user: added unit tests for new participation functions Part of MDL-59290. --- user/tests/userlib_test.php | 179 ++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) 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); + } }