示例#1
0
/**
 * Check whether a user has a particular capability in a given context.
 *
 * For example:
 *      $context = get_context_instance(CONTEXT_MODULE, $cm->id);
 *      has_capability('mod/forum:replypost',$context)
 *
 * By default checks the capabilities of the current user, but you can pass a
 * different userid. By default will return true for admin users, but you can override that with the fourth argument.
 *
 * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
 * or capabilities with XSS, config or data loss risks.
 *
 * @param string $capability the name of the capability to check. For example mod/forum:view
 * @param context $context the context to check the capability in. You normally get this with {@link get_context_instance}.
 * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
 * @param boolean $doanything If false, ignores effect of admin role assignment
 * @return boolean true if the user has this capability. Otherwise false.
 */
function has_capability($capability, context $context, $user = null, $doanything = true)
{
    global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
    if (during_initial_install()) {
        if ($SCRIPT === "/{$CFG->admin}/index.php" or $SCRIPT === "/{$CFG->admin}/cli/install.php" or $SCRIPT === "/{$CFG->admin}/cli/install_database.php") {
            // we are in an installer - roles can not work yet
            return true;
        } else {
            return false;
        }
    }
    if (strpos($capability, 'moodle/legacy:') === 0) {
        throw new coding_exception('Legacy capabilities can not be used any more!');
    }
    if (!is_bool($doanything)) {
        throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
    }
    // capability must exist
    if (!($capinfo = get_capability_info($capability))) {
        debugging('Capability "' . $capability . '" was not found! This has to be fixed in code.');
        return false;
    }
    if (!isset($USER->id)) {
        // should never happen
        $USER->id = 0;
    }
    // make sure there is a real user specified
    if ($user === null) {
        $userid = $USER->id;
    } else {
        $userid = is_object($user) ? $user->id : $user;
    }
    // make sure forcelogin cuts off not-logged-in users if enabled
    if (!empty($CFG->forcelogin) and $userid == 0) {
        return false;
    }
    // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
    if ($capinfo->captype === 'write' or $capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS)) {
        if (isguestuser($userid) or $userid == 0) {
            return false;
        }
    }
    // somehow make sure the user is not deleted and actually exists
    if ($userid != 0) {
        if ($userid == $USER->id and isset($USER->deleted)) {
            // this prevents one query per page, it is a bit of cheating,
            // but hopefully session is terminated properly once user is deleted
            if ($USER->deleted) {
                return false;
            }
        } else {
            if (!context_user::instance($userid, IGNORE_MISSING)) {
                // no user context == invalid userid
                return false;
            }
        }
    }
    // context path/depth must be valid
    if (empty($context->path) or $context->depth == 0) {
        // this should not happen often, each upgrade tries to rebuild the context paths
        debugging('Context id ' . $context->id . ' does not have valid path, please use build_context_path()');
        if (is_siteadmin($userid)) {
            return true;
        } else {
            return false;
        }
    }
    // Find out if user is admin - it is not possible to override the doanything in any way
    // and it is not possible to switch to admin role either.
    if ($doanything) {
        if (is_siteadmin($userid)) {
            if ($userid != $USER->id) {
                return true;
            }
            // make sure switchrole is not used in this context
            if (empty($USER->access['rsw'])) {
                return true;
            }
            $parts = explode('/', trim($context->path, '/'));
            $path = '';
            $switched = false;
            foreach ($parts as $part) {
                $path .= '/' . $part;
                if (!empty($USER->access['rsw'][$path])) {
                    $switched = true;
                    break;
                }
            }
            if (!$switched) {
                return true;
            }
            //ok, admin switched role in this context, let's use normal access control rules
        }
    }
    // Careful check for staleness...
    $context->reload_if_dirty();
    if ($USER->id == $userid) {
        if (!isset($USER->access)) {
            load_all_capabilities();
        }
        $access =& $USER->access;
    } else {
        // make sure user accessdata is really loaded
        get_user_accessdata($userid, true);
        $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
    }
    // Load accessdata for below-the-course context if necessary,
    // all contexts at and above all courses are already loaded
    if ($context->contextlevel != CONTEXT_COURSE and $coursecontext = $context->get_course_context(false)) {
        load_course_context($userid, $coursecontext, $access);
    }
    return has_capability_in_accessdata($capability, $context, $access);
}
示例#2
0
/**
 * Check whether a user has a particular capability in a given context.
 *
 * For example::
 *      $context = get_context_instance(CONTEXT_MODULE, $cm->id);
 *      has_capability('mod/forum:replypost',$context)
 *
 * By default checks the capabilities of the current user, but you can pass a
 * different userid. By default will return true for admin users, but you can override that with the fourth argument.
 *
 * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
 * or capabilities with XSS, config or data loss risks.
 *
 * @param string $capability the name of the capability to check. For example mod/forum:view
 * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
 * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
 * @param boolean $doanything If false, ignores effect of admin role assignment
 * @return boolean true if the user has this capability. Otherwise false.
 */
function has_capability($capability, $context, $user = null, $doanything = true)
{
    global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE;
    if (during_initial_install()) {
        if ($SCRIPT === "/{$CFG->admin}/index.php" or $SCRIPT === "/{$CFG->admin}/cliupgrade.php") {
            // we are in an installer - roles can not work yet
            return true;
        } else {
            return false;
        }
    }
    if (strpos($capability, 'moodle/legacy:') === 0) {
        throw new coding_exception('Legacy capabilities can not be used any more!');
    }
    // the original $CONTEXT here was hiding serious errors
    // for security reasons do not reuse previous context
    if (empty($context)) {
        debugging('Incorrect context specified');
        return false;
    }
    if (!is_bool($doanything)) {
        throw new coding_exception('Capability parameter "doanything" is wierd ("' . $doanything . '"). This has to be fixed in code.');
    }
    // make sure there is a real user specified
    if ($user === null) {
        $userid = isset($USER->id) ? $USER->id : 0;
    } else {
        $userid = is_object($user) ? $user->id : $user;
    }
    // capability must exist
    if (!($capinfo = get_capability_info($capability))) {
        debugging('Capability "' . $capability . '" was not found! This should be fixed in code.');
        return false;
    }
    // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
    if ($capinfo->captype === 'write' or (int) $capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS)) {
        if (isguestuser($userid) or $userid == 0) {
            return false;
        }
    }
    if (is_null($context->path) or $context->depth == 0) {
        //this should not happen
        $contexts = array(SYSCONTEXTID, $context->id);
        $context->path = '/' . SYSCONTEXTID . '/' . $context->id;
        debugging('Context id ' . $context->id . ' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
    } else {
        $contexts = explode('/', $context->path);
        array_shift($contexts);
    }
    if (CLI_SCRIPT && !isset($USER->access)) {
        // In cron, some modules setup a 'fake' $USER,
        // ensure we load the appropriate accessdata.
        if (isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
            $ACCESSLIB_PRIVATE->dirtycontexts = null;
            //load fresh dirty contexts
        } else {
            load_user_accessdata($userid);
            $ACCESSLIB_PRIVATE->dirtycontexts = array();
        }
        $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
    } else {
        if (isset($USER->id) && $USER->id == $userid && !isset($USER->access)) {
            // caps not loaded yet - better to load them to keep BC with 1.8
            // not-logged-in user or $USER object set up manually first time here
            load_all_capabilities();
            $ACCESSLIB_PRIVATE->accessdatabyuser = array();
            // reset the cache for other users too, the dirty contexts are empty now
            $ACCESSLIB_PRIVATE->roledefinitions = array();
        }
    }
    // Load dirty contexts list if needed
    if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
        if (isset($USER->access['time'])) {
            $ACCESSLIB_PRIVATE->dirtycontexts = get_dirty_contexts($USER->access['time']);
        } else {
            $ACCESSLIB_PRIVATE->dirtycontexts = array();
        }
    }
    // Careful check for staleness...
    if (count($ACCESSLIB_PRIVATE->dirtycontexts) !== 0 and is_contextpath_dirty($contexts, $ACCESSLIB_PRIVATE->dirtycontexts)) {
        // reload all capabilities - preserving loginas, roleswitches, etc
        // and then cleanup any marks of dirtyness... at least from our short
        // term memory! :-)
        $ACCESSLIB_PRIVATE->accessdatabyuser = array();
        $ACCESSLIB_PRIVATE->roledefinitions = array();
        if (CLI_SCRIPT) {
            load_user_accessdata($userid);
            $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
            $ACCESSLIB_PRIVATE->dirtycontexts = array();
        } else {
            reload_all_capabilities();
        }
    }
    // Find out if user is admin - it is not possible to override the doanything in any way
    // and it is not possible to switch to admin role either.
    if ($doanything) {
        if (is_siteadmin($userid)) {
            if ($userid != $USER->id) {
                return true;
            }
            // make sure switchrole is not used in this context
            if (empty($USER->access['rsw'])) {
                return true;
            }
            $parts = explode('/', trim($context->path, '/'));
            $path = '';
            $switched = false;
            foreach ($parts as $part) {
                $path .= '/' . $part;
                if (!empty($USER->access['rsw'][$path])) {
                    $switched = true;
                    break;
                }
            }
            if (!$switched) {
                return true;
            }
            //ok, admin switched role in this context, let's use normal access control rules
        }
    }
    // divulge how many times we are called
    //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
    if (isset($USER->id) && $USER->id == $userid) {
        // we must accept strings and integers in $userid
        //
        // For the logged in user, we have $USER->access
        // which will have all RAs and caps preloaded for
        // course and above contexts.
        //
        // Contexts below courses && contexts that do not
        // hang from courses are loaded into $USER->access
        // on demand, and listed in $USER->access[loaded]
        //
        if ($context->contextlevel <= CONTEXT_COURSE) {
            // Course and above are always preloaded
            return has_capability_in_accessdata($capability, $context, $USER->access);
        }
        // Load accessdata for below-the-course contexts
        if (!path_inaccessdata($context->path, $USER->access)) {
            // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
            // $bt = debug_backtrace();
            // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
            load_subcontext($USER->id, $context, $USER->access);
        }
        return has_capability_in_accessdata($capability, $context, $USER->access);
    }
    if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
        load_user_accessdata($userid);
    }
    if ($context->contextlevel <= CONTEXT_COURSE) {
        // Course and above are always preloaded
        return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
    }
    // Load accessdata for below-the-course contexts as needed
    if (!path_inaccessdata($context->path, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
        // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
        // $bt = debug_backtrace();
        // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
        load_subcontext($userid, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
    }
    return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
}
示例#3
0
/**
 * Get an array of courses (with magic extra bits)
 * where the accessdata and in DB enrolments show
 * that the cap requested is available.
 *
 * The main use is for get_my_courses().
 *
 * Notes
 *
 * - $fields is an array of fieldnames to ADD
 *   so name the fields you really need, which will
 *   be added and uniq'd
 *
 * - the course records have $c->context which is a fully
 *   valid context object. Saves you a query per course!
 *
 * - the course records have $c->categorypath to make
 *   category lookups cheap
 *
 * - current implementation is split in -
 *
 *   - if the user has the cap systemwide, stupidly
 *     grab *every* course for a capcheck. This eats
 *     a TON of bandwidth, specially on large sites
 *     with separate DBs...
 *
 *   - otherwise, fetch "likely" courses with a wide net
 *     that should get us _cheaply_ at least the courses we need, and some
 *     we won't - we get courses that...
 *      - are in a category where user has the cap
 *      - or where use has a role-assignment (any kind)
 *      - or where the course has an override on for this cap
 *
 *   - walk the courses recordset checking the caps oneach one
 *     the checks are all in memory and quite fast
 *     (though we could implement a specialised variant of the
 *     has_capability_in_accessdata() code to speed it up)
 *
 * @param string $capability - name of the capability
 * @param array  $accessdata - accessdata session array
 * @param bool   $doanything - if false, ignore do anything
 * @param string $sort - sorting fields - prefix each fieldname with "c."
 * @param array  $fields - additional fields you are interested in...
 * @param int    $limit  - set if you want to limit the number of courses
 * @return array $courses - ordered array of course objects - see notes above
 *
 */
function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort = 'c.sortorder ASC', $fields = NULL, $limit = 0)
{
    global $CFG, $DB;
    // Slim base fields, let callers ask for what they need...
    $basefields = array('id', 'sortorder', 'shortname', 'idnumber');
    if (!is_null($fields)) {
        $fields = array_merge($basefields, $fields);
        $fields = array_unique($fields);
    } else {
        $fields = $basefields;
    }
    // If any of the fields is '*', leave it alone, discarding the rest
    // to avoid ambiguous columns under some silly DBs. See MDL-18746 :-D
    if (in_array('*', $fields)) {
        $fields = array('*');
    }
    $coursefields = 'c.' . implode(',c.', $fields);
    $sort = trim($sort);
    if ($sort !== '') {
        $sort = "ORDER BY {$sort}";
    }
    $sysctx = get_context_instance(CONTEXT_SYSTEM);
    if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) {
        //
        // Apparently the user has the cap sitewide, so walk *every* course
        // (the cap checks are moderately fast, but this moves massive bandwidth w the db)
        // Yuck.
        //
        $sql = "SELECT {$coursefields},\n                       ctx.id AS ctxid, ctx.path AS ctxpath,\n                       ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,\n                       cc.path AS categorypath\n                  FROM {course} c\n                  JOIN {course_categories} cc\n                       ON c.category=cc.id\n                  JOIN {context} ctx\n                       ON (c.id=ctx.instanceid AND ctx.contextlevel=" . CONTEXT_COURSE . ")\n                 {$sort} ";
        $rs = $DB->get_recordset_sql($sql);
    } else {
        //
        // narrow down where we have the caps to a few contexts
        // this will be a combination of
        // - courses    where user has an explicit enrolment
        // - courses    that have an override (any status) on that capability
        // - categories where user has the rights (granted status) on that capability
        //
        $sql = "SELECT ctx.*\n                  FROM {context} ctx\n                 WHERE ctx.contextlevel=" . CONTEXT_COURSECAT . "\n              ORDER BY ctx.depth";
        $rs = $DB->get_recordset_sql($sql);
        $catpaths = array();
        foreach ($rs as $catctx) {
            if ($catctx->path != '' && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) {
                $catpaths[] = $catctx->path;
            }
        }
        $rs->close();
        $catclause = '';
        $params = array();
        if (count($catpaths)) {
            $cc = count($catpaths);
            for ($n = 0; $n < $cc; $n++) {
                $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'";
            }
            $catclause = 'WHERE (' . implode(' OR ', $catpaths) . ')';
        }
        unset($catpaths);
        $capany = '';
        if ($doanything) {
            $capany = " OR rc.capability=:doany";
            $params['doany'] = 'moodle/site:doanything';
        }
        /// UNION 3 queries:
        /// - user role assignments in courses
        /// - user capability (override - any status) in courses
        /// - user right (granted status) in categories (optionally executed)
        /// Enclosing the 3-UNION into an inline_view to avoid column names conflict and making the ORDER BY cross-db
        /// and to allow selection of TEXT columns in the query (MSSQL and Oracle limitation). MDL-16209
        $sql = "\n            SELECT {$coursefields}, ctxid, ctxpath, ctxdepth, ctxlevel, categorypath\n              FROM (\n                    SELECT c.id,\n                           ctx.id AS ctxid, ctx.path AS ctxpath,\n                           ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,\n                           cc.path AS categorypath\n                    FROM {course} c\n                    JOIN {course_categories} cc\n                      ON c.category=cc.id\n                    JOIN {context} ctx\n                      ON (c.id=ctx.instanceid AND ctx.contextlevel=" . CONTEXT_COURSE . ")\n                    JOIN {role_assignments} ra\n                      ON (ra.contextid=ctx.id AND ra.userid=:userid)\n                    UNION\n                    SELECT c.id,\n                           ctx.id AS ctxid, ctx.path AS ctxpath,\n                           ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,\n                           cc.path AS categorypath\n                    FROM {course} c\n                    JOIN {course_categories} cc\n                      ON c.category=cc.id\n                    JOIN {context} ctx\n                      ON (c.id=ctx.instanceid AND ctx.contextlevel=" . CONTEXT_COURSE . ")\n                    JOIN {role_capabilities} rc\n                      ON (rc.contextid=ctx.id AND (rc.capability=:cap {$capany})) ";
        if (!empty($catclause)) {
            /// If we have found the right in categories, add child courses here too
            $sql .= "\n                    UNION\n                    SELECT c.id,\n                           ctx.id AS ctxid, ctx.path AS ctxpath,\n                           ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,\n                           cc.path AS categorypath\n                    FROM {course} c\n                    JOIN {course_categories} cc\n                      ON c.category=cc.id\n                    JOIN {context} ctx\n                      ON (c.id=ctx.instanceid AND ctx.contextlevel=" . CONTEXT_COURSE . ")\n                    {$catclause}";
        }
        /// Close the inline_view and join with courses table to get requested $coursefields
        $sql .= "\n                ) inline_view\n                INNER JOIN {course} c\n                    ON inline_view.id = c.id";
        /// To keep cross-db we need to strip any prefix in the ORDER BY clause for queries using UNION
        $sql .= "\n                " . preg_replace('/[a-z]+\\./i', '', $sort);
        /// Add ORDER BY clause
        $params['userid'] = $userid;
        $params['cap'] = $cap;
        $rs = $DB->get_recordset_sql($sql, $params);
    }
    /// Confirm rights (granted capability) for each course returned
    $courses = array();
    $cc = 0;
    // keep count
    if ($rs) {
        foreach ($rs as $c) {
            // build the context obj
            $c = make_context_subobj($c);
            if (has_capability_in_accessdata($cap, $c->context, $accessdata, $doanything)) {
                $courses[] = $c;
                if ($limit > 0 && $cc++ > $limit) {
                    break;
                }
            }
        }
        $rs->close();
    }
    return $courses;
}
示例#4
0
function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort = 'c.sortorder ASC', $fields = NULL, $limit = 0)
{
    global $CFG;
    // Slim base fields, let callers ask for what they need...
    $basefields = array('id', 'sortorder', 'shortname', 'idnumber');
    if (!is_null($fields)) {
        $fields = array_merge($basefields, $fields);
        $fields = array_unique($fields);
    } else {
        $fields = $basefields;
    }
    $coursefields = 'c.' . implode(',c.', $fields);
    $sort = trim($sort);
    if ($sort !== '') {
        $sort = "ORDER BY {$sort}";
    }
    $sysctx = get_context_instance(CONTEXT_SYSTEM);
    if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) {
        //
        // Apparently the user has the cap sitewide, so walk *every* course
        // (the cap checks are moderately fast, but this moves massive bandwidth w the db)
        // Yuck.
        //
        $sql = "SELECT {$coursefields},\n                       ctx.id AS ctxid, ctx.path AS ctxpath,\n                       ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,\n                       cc.path AS categorypath\n                FROM {$CFG->prefix}course c\n                JOIN {$CFG->prefix}course_categories cc\n                  ON c.category=cc.id\n                JOIN {$CFG->prefix}context ctx \n                  ON (c.id=ctx.instanceid AND ctx.contextlevel=" . CONTEXT_COURSE . ")\n                {$sort} ";
        $rs = get_recordset_sql($sql);
    } else {
        //
        // narrow down where we have the caps to a few contexts
        // this will be a combination of
        // - categories where we have the rights
        // - courses    where we have an explicit enrolment OR that have an override
        //
        $sql = "SELECT ctx.*\n                FROM   {$CFG->prefix}context ctx\n                WHERE  ctx.contextlevel=" . CONTEXT_COURSECAT . "\n                ORDER BY ctx.depth";
        $rs = get_recordset_sql($sql);
        $catpaths = array();
        if ($rs->RecordCount()) {
            while ($catctx = rs_fetch_next_record($rs)) {
                if ($catctx->path != '' && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) {
                    $catpaths[] = $catctx->path;
                }
            }
        }
        rs_close($rs);
        $catclause = '';
        if (count($catpaths)) {
            $cc = count($catpaths);
            for ($n = 0; $n < $cc; $n++) {
                $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'";
            }
            $catclause = 'OR (' . implode(' OR ', $catpaths) . ')';
        }
        unset($catpaths);
        $capany = '';
        if ($doanything) {
            $capany = " OR rc.capability='moodle/site:doanything'";
        }
        //
        // Note here that we *have* to have the compound clauses
        // in the LEFT OUTER JOIN condition for them to return NULL
        // appropriately and narrow things down...
        //
        $sql = "SELECT {$coursefields},\n                       ctx.id AS ctxid, ctx.path AS ctxpath,\n                       ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,\n                       cc.path AS categorypath\n                FROM {$CFG->prefix}course c\n                JOIN {$CFG->prefix}course_categories cc\n                  ON c.category=cc.id\n                JOIN {$CFG->prefix}context ctx \n                  ON (c.id=ctx.instanceid AND ctx.contextlevel=" . CONTEXT_COURSE . ")\n                LEFT OUTER JOIN {$CFG->prefix}role_assignments ra\n                  ON (ra.contextid=ctx.id AND ra.userid={$userid})\n                LEFT OUTER JOIN {$CFG->prefix}role_capabilities rc\n                  ON (rc.contextid=ctx.id AND (rc.capability='{$cap}' {$capany}))\n                WHERE    ra.id IS NOT NULL\n                      OR rc.id IS NOT NULL\n                      {$catclause}\n                {$sort} ";
        $rs = get_recordset_sql($sql);
    }
    $courses = array();
    $cc = 0;
    // keep count
    if ($rs->RecordCount()) {
        while ($c = rs_fetch_next_record($rs)) {
            // build the context obj
            $c = make_context_subobj($c);
            if (has_capability_in_accessdata($cap, $c->context, $accessdata, $doanything)) {
                $courses[] = $c;
                if ($limit > 0 && $cc++ > $limit) {
                    break;
                }
            }
        }
    }
    rs_close($rs);
    return $courses;
}