From: martinlanghoff Date: Wed, 19 Sep 2007 07:26:42 +0000 (+0000) Subject: datalib: Introducing get_courses_wmanagers() for print_courses() usage X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=70f158789ef5c0c636c8d3902ab7c0639c95a38b;p=moodle.git datalib: Introducing get_courses_wmanagers() for print_courses() usage For an efficient print_courses() we need to grab in a constant number of queries... - course data - "course manager" role assignments - user records for the coursemanagers' fullname() So here we do it in 2 DB queries. The 2nd one (grabbing RAs and user records) can be expensive if we are dealing with a large number of courses. Which we shouldn't - When the number of courses is large the course listing doesn't come this way anyway... --- diff --git a/lib/datalib.php b/lib/datalib.php index 5e5e4d8e91..50a71f7a2b 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -591,6 +591,205 @@ function get_courses_page($categoryid="all", $sort="c.sortorder ASC", $fields="c */ } +/* + * Retrieve course records with the course managers and other related records + * that we need for print_course(). This allows print_courses() to do its job + * in a constant number of DB queries, regardless of the number of courses, + * role assignments, etc. + * + * The returned array is indexed on c.id, and each course will have + * - $course->context - a context obj + * - $course->managers - array containing RA objects that include a $user obj + * with the minimal fields needed for fullname() + * + */ +function get_courses_wmanagers($categoryid=0, $sort="c.sortorder ASC", $fields=array()) { + /* + * The plan is to + * + * - Grab the courses JOINed w/context + * + * - Grab the interesting course-manager RAs + * JOINed with a base user obj and add them to each course + * + * So as to do all the work in 2 DB queries. The RA+user JOIN + * ends up being pretty expensive if it happens over _all_ + * courses on a large site. (Are we surprised!?) + * + * So this should _never_ get called with 'all' on a large site. + * + */ + global $USER, $CFG; + + $allcats = false; // bool flag + if ($categoryid === 'all') { + $categoryclause = ''; + $allcats = true; + } elseif (is_numeric($categoryid)) { + $categoryclause = "c.category = $categoryid"; + } else { + debugging("Could not recognise categoryid = $categoryid"); + $categoryclause = ''; + } + + $basefields = array('id', 'category', 'sortorder', + 'shortname', 'fullname', 'idnumber', + 'teacher', 'teachers', 'student', 'students', + 'guest', 'startdate', 'visible', + 'newsitems', 'cost', 'enrol', + 'groupmode', 'groupmodeforce'); + + if (!is_null($fields) && is_string($fields)) { + if (empty($fields)) { + $fields = $basefields; + } else { + // turn the fields from a string to an array that + // get_user_courses_bycap() will like... + $fields = explode(',',$fields); + $fields = array_map('trim', $fields); + $fields = array_unique(array_merge($basefields, $fields)); + } + } elseif (is_array($fields)) { + $fields = array_merge($basefields,$fields); + } + $coursefields = 'c.' .join(',c.', $fields); + + if (empty($sort)) { + $sortstatement = ""; + } else { + $sortstatement = "ORDER BY $sort"; + } + + $where = ''; + if ($categoryclause !== ''){ + $where = "WHERE $categoryclause"; + } + + // pull out all courses matching the cat + $sql = "SELECT $coursefields, + ctx.id AS ctxid, ctx.path AS ctxpath, ctx.depth as ctxdepth + FROM {$CFG->prefix}course c + JOIN {$CFG->prefix}context ctx + ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") + $where + $sortstatement"; + + $catpaths = array(); + $catpath = NULL; + if ($courses = get_records_sql($sql)) { + // loop on courses materialising + // the context, and prepping data to fetch the + // managers efficiently later... + foreach ($courses as $k => $course) { + $courses[$k] = make_context_subobj($courses[$k]); + $courses[$k]->managers = array(); + if ($allcats === false) { + // single cat, so take just the first one... + if ($catpath === NULL) { + $catpath = preg_replace(':/\d+$:', '',$courses[$k]->context->path); + } + } else { + // chop off the contextid of the course itself + // like dirname() does... + $catpaths[] = preg_replace(':/\d+$:', '',$courses[$k]->context->path); + } + } + } else { + return array(); // no courses! + } + + $managerroles = split(',', $CFG->coursemanager); + $catctxids = ''; + if (count($managerroles)) { + if ($allcats === true) { + $catpaths = array_unique($catpaths); + $ctxids = array(); + foreach ($catpaths as $cpath) { + $ctxids = array_merge($ctxids, explode('/',substr($cpath,1))); + } + $ctxids = array_unique($ctxids); + $catctxids = implode( ',' , $ctxids); + unset($catpaths);unset($cpath); + } else { + // take the ctx path from the first course + // as all categories will be the same... + $catpath = substr($catpath,1); + $catpath = preg_replace(':/\d+$:','',$catpath); + $catctxids = str_replace('/',',',$catpath); + } + if ($categoryclause !== '') { + $categoryclause = "AND $categoryclause"; + } + /* + * Note: Here we use a LEFT OUTER JOIN that can + * "optionally" match to avoid passing a ton of context + * ids in an IN() clause. Perhaps a subselect is faster. + * + * In any case, this SQL is not-so-nice over large sets of + * courses with no $categoryclause. + * + */ + $sql = "SELECT ctx.path, ctx.instanceid, ctx.contextlevel, + ra.hidden, + r.id AS roleid, r.name as rolename, + u.id AS userid, u.firstname, u.lastname + FROM {$CFG->prefix}role_assignments ra + JOIN {$CFG->prefix}context ctx + ON ra.contextid = ctx.id + JOIN {$CFG->prefix}user u + ON ra.userid = u.id + JOIN {$CFG->prefix}role r + ON ra.roleid = r.id + LEFT OUTER JOIN {$CFG->prefix}course c + ON (ctx.instanceid=c.id AND ctx.contextlevel=".CONTEXT_COURSE.") + WHERE ( c.id IS NOT NULL + OR ra.contextid IN ($catctxids) ) + AND ra.roleid IN ({$CFG->coursemanager}) + $categoryclause + ORDER BY r.sortorder ASC, ctx.contextlevel ASC, ra.sortorder ASC"; + + $rs = get_recordset_sql($sql); + + // This loop is fairly stupid as it stands - might get better + // results doing an initial pass clustering RAs by path. + if ($rs->RecordCount()) { + while ($ra = rs_fetch_next_record($rs)) { + $user = new StdClass; + $user->id = $ra->userid; unset($ra->userid); + $user->firstname = $ra->firstname; unset($ra->firstname); + $user->lastname = $ra->lastname; unset($ra->lastname); + $ra->user = $user; + if ($ra->contextlevel == CONTEXT_SYSTEM) { + foreach ($courses as $k => $course) { + $courses[$k]->managers[] = $ra; + } + } elseif ($ra->contextlevel == CONTEXT_COURSECAT) { + if ($allcats === false) { + // It always applies + foreach ($courses as $k => $course) { + $courses[$k]->managers[] = $ra; + } + } else { + foreach ($courses as $k => $course) { + // Note that strpos() returns 0 as "matched at pos 0" + if (strpos($course->context->path, $ra->path.'/')===0) { + // Only add it to subpaths + $courses[$k]->managers[] = $ra; + } + } + } + } else { // course-level + $courses[$ra->instanceid]->managers[] = $ra; + } + } + } + + + + } + + return $courses; +} /** * Convenience function - lists courses that a user has access to view.