Commit | Line | Data |
---|---|---|
3564771d | 1 | <?php |
2 | ||
117bd748 PS |
3 | // This file is part of Moodle - http://moodle.org/ |
4 | // | |
3564771d | 5 | // Moodle is free software: you can redistribute it and/or modify |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
117bd748 | 14 | // |
3564771d | 15 | // You should have received a copy of the GNU General Public License |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
341b5ed2 | 17 | |
7cf1c7bd | 18 | /** |
19 | * Library of functions for database manipulation. | |
5930cded | 20 | * |
7cf1c7bd | 21 | * Other main libraries: |
22 | * - weblib.php - functions that produce web output | |
23 | * - moodlelib.php - general-purpose Moodle functions | |
3564771d | 24 | * |
25 | * @package moodlecore | |
26 | * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} | |
27 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
7cf1c7bd | 28 | */ |
29 | ||
4f0c2d00 PS |
30 | /** |
31 | * The maximum courses in a category | |
32 | * MAX_COURSES_IN_CATEGORY * MAX_COURSE_CATEGORIES must not be more than max integer! | |
33 | */ | |
3564771d | 34 | define('MAX_COURSES_IN_CATEGORY', 10000); |
4f0c2d00 | 35 | |
117bd748 | 36 | /** |
3564771d | 37 | * The maximum number of course categories |
117bd748 | 38 | * MAX_COURSES_IN_CATEGORY * MAX_COURSE_CATEGORIES must not be more than max integer! |
3564771d | 39 | */ |
0cbe8111 | 40 | define('MAX_COURSE_CATEGORIES', 10000); |
41 | ||
4f0c2d00 PS |
42 | /** |
43 | * Number of seconds to wait before updating lastaccess information in DB. | |
44 | */ | |
45 | define('LASTACCESS_UPDATE_SECS', 60); | |
df28d6c5 | 46 | |
18a97fd8 | 47 | /** |
fbc21ae8 | 48 | * Returns $user object of the main admin user |
20aeb4b8 | 49 | * primary admin = admin with lowest role_assignment id among admins |
3564771d | 50 | * |
51 | * @global object | |
52 | * @static object $myadmin | |
53 | * @return object An associative array representing the admin user. | |
fbc21ae8 | 54 | */ |
4f0c2d00 PS |
55 | function get_admin() { |
56 | static $mainadmin = null; | |
2965f8fd | 57 | |
4f0c2d00 | 58 | if (!isset($mainadmin)) { |
850262ee AD |
59 | if (! $admins = get_admins()) { |
60 | return false; | |
df28d6c5 | 61 | } |
4f0c2d00 PS |
62 | //TODO: add some admin setting for specifying of THE main admin |
63 | // for now return the first assigned admin | |
64 | $mainadmin = reset($admins); | |
df28d6c5 | 65 | } |
4f0c2d00 | 66 | return $mainadmin; |
df28d6c5 | 67 | } |
68 | ||
18a97fd8 | 69 | /** |
4f0c2d00 | 70 | * Returns list of all admins, using 1 DB query |
fbc21ae8 | 71 | * |
3564771d | 72 | * @return array |
fbc21ae8 | 73 | */ |
df28d6c5 | 74 | function get_admins() { |
4f0c2d00 | 75 | global $DB, $CFG; |
5930cded | 76 | |
4f0c2d00 | 77 | $sql = "SELECT u.* |
624a690b | 78 | FROM {user} u |
4f0c2d00 | 79 | WHERE u.deleted = 0 AND u.id IN ($CFG->siteadmins)"; |
5930cded | 80 | |
4f0c2d00 | 81 | return $DB->get_records_sql($sql); |
df28d6c5 | 82 | } |
83 | ||
3564771d | 84 | /** |
85 | * Get all of the courses in a given meta course | |
86 | * | |
87 | * @global object | |
88 | * @param int $metacourseid The metacourse id | |
89 | * @return array | |
90 | */ | |
b61efafb | 91 | function get_courses_in_metacourse($metacourseid) { |
624a690b | 92 | global $DB; |
b61efafb | 93 | |
624a690b | 94 | $sql = "SELECT c.id, c.shortname, c.fullname |
95 | FROM {course} c, {course_meta} mc | |
96 | WHERE mc.parent_course = ? AND mc.child_course = c.id | |
97 | ORDER BY c.shortname"; | |
98 | $params = array($metacourseid); | |
b61efafb | 99 | |
624a690b | 100 | return $DB->get_records_sql($sql, $params); |
b61efafb | 101 | } |
102 | ||
3564771d | 103 | /** |
104 | * @todo Document this function | |
105 | * | |
106 | * @global object | |
107 | * @uses SITEID | |
108 | * @param int $metacourseid | |
109 | * @return array | |
110 | */ | |
624a690b | 111 | function get_courses_notin_metacourse($metacourseid) { |
112 | global $DB; | |
b61efafb | 113 | |
624a690b | 114 | if ($alreadycourses = get_courses_in_metacourse($metacourseid)) { |
115 | $alreadycourses = implode(',',array_keys($alreadycourses)); | |
116 | $alreadycourses = "AND c.id NOT IN ($alreadycourses)"; | |
c44d5d42 | 117 | } else { |
624a690b | 118 | $alreadycourses = ""; |
b61efafb | 119 | } |
178ccd11 | 120 | |
624a690b | 121 | $sql = "SELECT c.id,c.shortname,c.fullname |
122 | FROM {course} c | |
123 | WHERE c.id != ? and c.id != ".SITEID." and c.metacourse != 1 | |
124 | $alreadycourses | |
125 | ORDER BY c.shortname"; | |
126 | $params = array($metacourseid); | |
5930cded | 127 | |
624a690b | 128 | return $DB->get_records_sql($sql, $params); |
b61efafb | 129 | } |
130 | ||
3564771d | 131 | /** |
132 | * @todo Document this function | |
133 | * | |
134 | * This function is nearly identical to {@link get_courses_notin_metacourse()} | |
135 | * | |
136 | * @global object | |
137 | * @uses SITEID | |
138 | * @param int $metacourseid | |
139 | * @return int The count | |
140 | */ | |
493cde24 | 141 | function count_courses_notin_metacourse($metacourseid) { |
624a690b | 142 | global $DB; |
493cde24 | 143 | |
624a690b | 144 | if ($alreadycourses = get_courses_in_metacourse($metacourseid)) { |
145 | $alreadycourses = implode(',',array_keys($alreadycourses)); | |
146 | $alreadycourses = "AND c.id NOT IN ($alreadycourses)"; | |
147 | } else { | |
148 | $alreadycourses = ""; | |
493cde24 | 149 | } |
150 | ||
d251907c | 151 | $sql = "SELECT COUNT(c.id) |
624a690b | 152 | FROM {course} c |
153 | WHERE c.id != ? and c.id != ".SITEID." and c.metacourse != 1 | |
154 | $alreadycourses"; | |
155 | $params = array($metacourseid); | |
156 | ||
157 | return $DB->count_records_sql($sql, $params); | |
493cde24 | 158 | } |
159 | ||
900df8b6 | 160 | /** |
fbc21ae8 | 161 | * Search through course users |
162 | * | |
5930cded | 163 | * If $coursid specifies the site course then this function searches |
fbc21ae8 | 164 | * through all undeleted and confirmed users |
165 | * | |
3564771d | 166 | * @global object |
167 | * @uses SITEID | |
168 | * @uses SQL_PARAMS_NAMED | |
169 | * @uses CONTEXT_COURSE | |
fbc21ae8 | 170 | * @param int $courseid The course in question. |
171 | * @param int $groupid The group in question. | |
3564771d | 172 | * @param string $searchtext The string to search for |
173 | * @param string $sort A field to sort by | |
174 | * @param array $exceptions A list of IDs to ignore, eg 2,4,5,8,9,10 | |
175 | * @return array | |
fbc21ae8 | 176 | */ |
624a690b | 177 | function search_users($courseid, $groupid, $searchtext, $sort='', array $exceptions=null) { |
178 | global $DB; | |
0720313b | 179 | |
245ac557 | 180 | $LIKE = $DB->sql_ilike(); |
181 | $fullname = $DB->sql_fullname('u.firstname', 'u.lastname'); | |
8f0cd6ef | 182 | |
900df8b6 | 183 | if (!empty($exceptions)) { |
624a690b | 184 | list($exceptions, $params) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'ex0000', false); |
185 | $except = "AND u.id $exceptions"; | |
900df8b6 | 186 | } else { |
624a690b | 187 | $except = ""; |
188 | $params = array(); | |
900df8b6 | 189 | } |
2700d113 | 190 | |
900df8b6 | 191 | if (!empty($sort)) { |
624a690b | 192 | $order = "ORDER BY $sort"; |
900df8b6 | 193 | } else { |
624a690b | 194 | $order = ""; |
900df8b6 | 195 | } |
8f0cd6ef | 196 | |
624a690b | 197 | $select = "u.deleted = 0 AND u.confirmed = 1 AND ($fullname $LIKE :search1 OR u.email $LIKE :search2)"; |
198 | $params['search1'] = "%$searchtext%"; | |
199 | $params['search2'] = "%$searchtext%"; | |
2700d113 | 200 | |
222ac91b | 201 | if (!$courseid or $courseid == SITEID) { |
624a690b | 202 | $sql = "SELECT u.id, u.firstname, u.lastname, u.email |
203 | FROM {user} u | |
204 | WHERE $select | |
205 | $except | |
206 | $order"; | |
207 | return $DB->get_records_sql($sql, $params); | |
2700d113 | 208 | |
624a690b | 209 | } else { |
900df8b6 | 210 | if ($groupid) { |
624a690b | 211 | $sql = "SELECT u.id, u.firstname, u.lastname, u.email |
212 | FROM {user} u | |
213 | JOIN {groups_members} gm ON gm.userid = u.id | |
214 | WHERE $select AND gm.groupid = :groupid | |
215 | $except | |
216 | $order"; | |
217 | $params['groupid'] = $groupid; | |
218 | return $DB->get_records_sql($sql, $params); | |
219 | ||
900df8b6 | 220 | } else { |
ea8158c1 | 221 | $context = get_context_instance(CONTEXT_COURSE, $courseid); |
222 | $contextlists = get_related_contexts_string($context); | |
624a690b | 223 | |
224 | $sql = "SELECT u.id, u.firstname, u.lastname, u.email | |
225 | FROM {user} u | |
226 | JOIN {role_assignments} ra ON ra.userid = u.id | |
227 | WHERE $select AND ra.contextid $contextlists | |
228 | $except | |
229 | $order"; | |
230 | return $DB->get_records_sql($sql, $params); | |
900df8b6 | 231 | } |
232 | } | |
df28d6c5 | 233 | } |
234 | ||
18a97fd8 | 235 | /** |
fbc21ae8 | 236 | * Returns a subset of users |
237 | * | |
3564771d | 238 | * @global object |
239 | * @uses DEBUG_DEVELOPER | |
240 | * @uses SQL_PARAMS_NAMED | |
7290c7fa | 241 | * @param bool $get If false then only a count of the records is returned |
fbc21ae8 | 242 | * @param string $search A simple string to search for |
7290c7fa | 243 | * @param bool $confirmed A switch to allow/disallow unconfirmed users |
3564771d | 244 | * @param array $exceptions A list of IDs to ignore, eg 2,4,5,8,9,10 |
fbc21ae8 | 245 | * @param string $sort A SQL snippet for the sorting criteria to use |
3564771d | 246 | * @param string $firstinitial Users whose first name starts with $firstinitial |
247 | * @param string $lastinitial Users whose last name starts with $lastinitial | |
248 | * @param string $page The page or records to return | |
249 | * @param string $recordsperpage The number of records to return per page | |
fbc21ae8 | 250 | * @param string $fields A comma separated list of fields to be returned from the chosen table. |
117bd748 | 251 | * @return array|int|bool {@link $USER} records unless get is false in which case the integer count of the records found is returned. |
3564771d | 252 | * False is returned if an error is encountered. |
fbc21ae8 | 253 | */ |
624a690b | 254 | function get_users($get=true, $search='', $confirmed=false, array $exceptions=null, $sort='firstname ASC', |
255 | $firstinitial='', $lastinitial='', $page='', $recordsperpage='', $fields='*', $extraselect='', array $extraparams=null) { | |
256 | global $DB; | |
5930cded | 257 | |
36075e09 | 258 | if ($get && !$recordsperpage) { |
259 | debugging('Call to get_users with $get = true no $recordsperpage limit. ' . | |
260 | 'On large installations, this will probably cause an out of memory error. ' . | |
261 | 'Please think again and change your code so that it does not try to ' . | |
03517306 | 262 | 'load so much data into memory.', DEBUG_DEVELOPER); |
36075e09 | 263 | } |
18a97fd8 | 264 | |
245ac557 | 265 | $LIKE = $DB->sql_ilike(); |
266 | $fullname = $DB->sql_fullname(); | |
e384fb7b | 267 | |
624a690b | 268 | $select = " username <> :guest AND deleted = 0"; |
269 | $params = array('guest'=>'guest'); | |
488acd1b | 270 | |
0044147e | 271 | if (!empty($search)){ |
272 | $search = trim($search); | |
624a690b | 273 | $select .= " AND ($fullname $LIKE :search1 OR email $LIKE :search2 OR username = :search3)"; |
274 | $params['search1'] = "%$search%"; | |
275 | $params['search2'] = "%$search%"; | |
276 | $params['search3'] = "$search"; | |
e384fb7b | 277 | } |
278 | ||
5a741655 | 279 | if ($confirmed) { |
624a690b | 280 | $select .= " AND confirmed = 1"; |
5a741655 | 281 | } |
282 | ||
283 | if ($exceptions) { | |
624a690b | 284 | list($exceptions, $eparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'ex0000', false); |
285 | $params = $params + $eparams; | |
286 | $except = " AND id $exceptions"; | |
5a741655 | 287 | } |
288 | ||
488acd1b | 289 | if ($firstinitial) { |
624a690b | 290 | $select .= " AND firstname $LIKE :fni"; |
291 | $params['fni'] = "$firstinitial%"; | |
8f0cd6ef | 292 | } |
488acd1b | 293 | if ($lastinitial) { |
624a690b | 294 | $select .= " AND lastname $LIKE :lni"; |
295 | $params['lni'] = "$lastinitial%"; | |
8f0cd6ef | 296 | } |
488acd1b | 297 | |
cd1edf9e | 298 | if ($extraselect) { |
624a690b | 299 | $select .= " AND $extraselect"; |
300 | $params = $params + (array)$extraparams; | |
cd1edf9e | 301 | } |
302 | ||
5a741655 | 303 | if ($get) { |
624a690b | 304 | return $DB->get_records_select('user', $select, $params, $sort, $fields, $page, $recordsperpage); |
5a741655 | 305 | } else { |
624a690b | 306 | return $DB->count_records_select('user', $select, $params); |
5a741655 | 307 | } |
9fa49e22 | 308 | } |
309 | ||
5a741655 | 310 | |
18a97fd8 | 311 | /** |
fbc21ae8 | 312 | * @todo Finish documenting this function |
3564771d | 313 | * |
314 | * @param string $sort An SQL field to sort by | |
315 | * @param string $dir The sort direction ASC|DESC | |
316 | * @param int $page The page or records to return | |
317 | * @param int $recordsperpage The number of records to return per page | |
318 | * @param string $search A simple string to search for | |
319 | * @param string $firstinitial Users whose first name starts with $firstinitial | |
320 | * @param string $lastinitial Users whose last name starts with $lastinitial | |
321 | * @param string $extraselect An additional SQL select statement to append to the query | |
322 | * @param array $extraparams Additional parameters to use for the above $extraselect | |
323 | * @return array Array of {@link $USER} records | |
fbc21ae8 | 324 | */ |
325 | ||
36075e09 | 326 | function get_users_listing($sort='lastaccess', $dir='ASC', $page=0, $recordsperpage=0, |
624a690b | 327 | $search='', $firstinitial='', $lastinitial='', $extraselect='', array $extraparams=null) { |
328 | global $DB; | |
31fefa63 | 329 | |
245ac557 | 330 | $LIKE = $DB->sql_ilike(); |
331 | $fullname = $DB->sql_fullname(); | |
c2a96d6b | 332 | |
624a690b | 333 | $select = "deleted <> 1"; |
334 | $params = array(); | |
488acd1b | 335 | |
0044147e | 336 | if (!empty($search)) { |
337 | $search = trim($search); | |
624a690b | 338 | $select .= " AND ($fullname $LIKE :search1 OR email $LIKE :search2 OR username = :search3)"; |
339 | $params['search1'] = "%$search%"; | |
340 | $params['search2'] = "%$search%"; | |
341 | $params['search3'] = "$search"; | |
488acd1b | 342 | } |
343 | ||
344 | if ($firstinitial) { | |
624a690b | 345 | $select .= " AND firstname $LIKE :fni"; |
346 | $params['fni'] = "$firstinitial%"; | |
488acd1b | 347 | } |
488acd1b | 348 | if ($lastinitial) { |
624a690b | 349 | $select .= " AND lastname $LIKE :lni"; |
350 | $params['lni'] = "$lastinitial%"; | |
c750592a | 351 | } |
352 | ||
cd1edf9e | 353 | if ($extraselect) { |
624a690b | 354 | $select .= " AND $extraselect"; |
355 | $params = $params + (array)$extraparams; | |
cd1edf9e | 356 | } |
03d820c7 | 357 | |
488acd1b | 358 | if ($sort) { |
624a690b | 359 | $sort = " ORDER BY $sort $dir"; |
488acd1b | 360 | } |
361 | ||
362 | /// warning: will return UNCONFIRMED USERS | |
624a690b | 363 | return $DB->get_records_sql("SELECT id, username, email, firstname, lastname, city, country, lastaccess, confirmed, mnethostid |
364 | FROM {user} | |
365 | WHERE $select | |
366 | $sort", $params, $page, $recordsperpage); | |
9fa49e22 | 367 | |
368 | } | |
369 | ||
488acd1b | 370 | |
18a97fd8 | 371 | /** |
7290c7fa | 372 | * Full list of users that have confirmed their accounts. |
fbc21ae8 | 373 | * |
3564771d | 374 | * @global object |
624a690b | 375 | * @return array of unconfirmed users |
fbc21ae8 | 376 | */ |
9fa49e22 | 377 | function get_users_confirmed() { |
624a690b | 378 | global $DB; |
379 | return $DB->get_records_sql("SELECT * | |
380 | FROM {user} | |
381 | WHERE confirmed = 1 AND deleted = 0 AND username <> ?", array('guest')); | |
9fa49e22 | 382 | } |
383 | ||
384 | ||
02ebf404 | 385 | /// OTHER SITE AND COURSE FUNCTIONS ///////////////////////////////////////////// |
386 | ||
387 | ||
18a97fd8 | 388 | /** |
fbc21ae8 | 389 | * Returns $course object of the top-level site. |
390 | * | |
3f77c158 | 391 | * @return object A {@link $COURSE} object for the site, exception if not found |
fbc21ae8 | 392 | */ |
c44d5d42 | 393 | function get_site() { |
624a690b | 394 | global $SITE, $DB; |
c44d5d42 | 395 | |
396 | if (!empty($SITE->id)) { // We already have a global to use, so return that | |
397 | return $SITE; | |
398 | } | |
02ebf404 | 399 | |
624a690b | 400 | if ($course = $DB->get_record('course', array('category'=>0))) { |
02ebf404 | 401 | return $course; |
402 | } else { | |
3f77c158 PS |
403 | // course table exists, but the site is not there, |
404 | // unfortunately there is no automatic way to recover | |
405 | throw new moodle_exception('nosite', 'error'); | |
02ebf404 | 406 | } |
407 | } | |
408 | ||
18a97fd8 | 409 | /** |
613bbd7c | 410 | * Returns list of courses, for whole site, or category |
411 | * | |
412 | * Returns list of courses, for whole site, or category | |
bfbfdb53 | 413 | * Important: Using c.* for fields is extremely expensive because |
613bbd7c | 414 | * we are using distinct. You almost _NEVER_ need all the fields |
415 | * in such a large SELECT | |
416 | * | |
3564771d | 417 | * @global object |
418 | * @global object | |
419 | * @global object | |
420 | * @uses CONTEXT_COURSE | |
421 | * @param string|int $categoryid Either a category id or 'all' for everything | |
422 | * @param string $sort A field and direction to sort by | |
423 | * @param string $fields The additional fields to return | |
424 | * @return array Array of courses | |
613bbd7c | 425 | */ |
6315b1c8 | 426 | function get_courses($categoryid="all", $sort="c.sortorder ASC", $fields="c.*") { |
02ebf404 | 427 | |
3b8a284c | 428 | global $USER, $CFG, $DB; |
5930cded | 429 | |
3b8a284c | 430 | $params = array(); |
431 | ||
432 | if ($categoryid !== "all" && is_numeric($categoryid)) { | |
433 | $categoryselect = "WHERE c.category = :catid"; | |
434 | $params['catid'] = $categoryid; | |
71dea306 | 435 | } else { |
5930cded | 436 | $categoryselect = ""; |
09575480 | 437 | } |
438 | ||
439 | if (empty($sort)) { | |
440 | $sortstatement = ""; | |
441 | } else { | |
442 | $sortstatement = "ORDER BY $sort"; | |
443 | } | |
444 | ||
445 | $visiblecourses = array(); | |
5930cded | 446 | |
4f0c2d00 PS |
447 | list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); |
448 | ||
449 | $sql = "SELECT $fields $ccselect | |
3b8a284c | 450 | FROM {course} c |
4f0c2d00 | 451 | $ccjoin |
3b8a284c | 452 | $categoryselect |
453 | $sortstatement"; | |
454 | ||
71dea306 | 455 | // pull out all course matching the cat |
3b8a284c | 456 | if ($courses = $DB->get_records_sql($sql, $params)) { |
09575480 | 457 | |
458 | // loop throught them | |
459 | foreach ($courses as $course) { | |
4f0c2d00 | 460 | context_instance_preload($course); |
285f94f5 | 461 | if (isset($course->visible) && $course->visible <= 0) { |
09575480 | 462 | // for hidden courses, require visibility check |
4f0c2d00 | 463 | if (has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) { |
3b8a284c | 464 | $visiblecourses [$course->id] = $course; |
09575480 | 465 | } |
466 | } else { | |
3b8a284c | 467 | $visiblecourses [$course->id] = $course; |
5930cded | 468 | } |
09575480 | 469 | } |
6315b1c8 | 470 | } |
71dea306 | 471 | return $visiblecourses; |
8130b77b | 472 | } |
473 | ||
8130b77b | 474 | |
6315b1c8 | 475 | /** |
613bbd7c | 476 | * Returns list of courses, for whole site, or category |
477 | * | |
478 | * Similar to get_courses, but allows paging | |
5930cded | 479 | * Important: Using c.* for fields is extremely expensive because |
613bbd7c | 480 | * we are using distinct. You almost _NEVER_ need all the fields |
481 | * in such a large SELECT | |
482 | * | |
3564771d | 483 | * @global object |
484 | * @global object | |
485 | * @global object | |
486 | * @uses CONTEXT_COURSE | |
487 | * @param string|int $categoryid Either a category id or 'all' for everything | |
488 | * @param string $sort A field and direction to sort by | |
489 | * @param string $fields The additional fields to return | |
490 | * @param int $totalcount Reference for the number of courses | |
491 | * @param string $limitfrom The course to start from | |
492 | * @param string $limitnum The number of courses to limit to | |
117bd748 | 493 | * @return array Array of courses |
613bbd7c | 494 | */ |
6315b1c8 | 495 | function get_courses_page($categoryid="all", $sort="c.sortorder ASC", $fields="c.*", |
496 | &$totalcount, $limitfrom="", $limitnum="") { | |
3b8a284c | 497 | global $USER, $CFG, $DB; |
c7fe5c6f | 498 | |
3b8a284c | 499 | $params = array(); |
5930cded | 500 | |
71dea306 | 501 | $categoryselect = ""; |
502 | if ($categoryid != "all" && is_numeric($categoryid)) { | |
3b8a284c | 503 | $categoryselect = "WHERE c.category = :catid"; |
504 | $params['catid'] = $categoryid; | |
71dea306 | 505 | } else { |
5930cded | 506 | $categoryselect = ""; |
507 | } | |
508 | ||
4f0c2d00 PS |
509 | list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); |
510 | ||
511 | $sql = "SELECT $fields $ccselect | |
3b8a284c | 512 | FROM {course} c |
4f0c2d00 | 513 | $ccjoin |
3b8a284c | 514 | $categoryselect |
515 | ORDER BY $sort"; | |
516 | ||
71dea306 | 517 | // pull out all course matching the cat |
3b8a284c | 518 | if (!$rs = $DB->get_recordset_sql($sql, $params)) { |
519 | return array(); | |
12490fc2 | 520 | } |
71dea306 | 521 | $totalcount = 0; |
5930cded | 522 | |
285f94f5 | 523 | if (!$limitfrom) { |
5930cded | 524 | $limitfrom = 0; |
71dea306 | 525 | } |
5930cded | 526 | |
71dea306 | 527 | // iteration will have to be done inside loop to keep track of the limitfrom and limitnum |
3b8a284c | 528 | $visiblecourses = array(); |
529 | foreach($rs as $course) { | |
4f0c2d00 | 530 | context_instance_preload($course); |
03cedd62 | 531 | if ($course->visible <= 0) { |
532 | // for hidden courses, require visibility check | |
4f0c2d00 | 533 | if (has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) { |
71dea306 | 534 | $totalcount++; |
03cedd62 | 535 | if ($totalcount > $limitfrom && (!$limitnum or count($visiblecourses) < $limitnum)) { |
3b8a284c | 536 | $visiblecourses [$course->id] = $course; |
71dea306 | 537 | } |
538 | } | |
03cedd62 | 539 | } else { |
540 | $totalcount++; | |
541 | if ($totalcount > $limitfrom && (!$limitnum or count($visiblecourses) < $limitnum)) { | |
3b8a284c | 542 | $visiblecourses [$course->id] = $course; |
03cedd62 | 543 | } |
5930cded | 544 | } |
71dea306 | 545 | } |
3b8a284c | 546 | $rs->close(); |
71dea306 | 547 | return $visiblecourses; |
02ebf404 | 548 | } |
549 | ||
624a690b | 550 | /** |
70f15878 | 551 | * Retrieve course records with the course managers and other related records |
552 | * that we need for print_course(). This allows print_courses() to do its job | |
553 | * in a constant number of DB queries, regardless of the number of courses, | |
554 | * role assignments, etc. | |
bfbfdb53 | 555 | * |
70f15878 | 556 | * The returned array is indexed on c.id, and each course will have |
70f15878 | 557 | * - $course->managers - array containing RA objects that include a $user obj |
558 | * with the minimal fields needed for fullname() | |
559 | * | |
3564771d | 560 | * @global object |
561 | * @global object | |
562 | * @global object | |
563 | * @uses CONTEXT_COURSE | |
564 | * @uses CONTEXT_SYSTEM | |
565 | * @uses CONTEXT_COURSECAT | |
566 | * @uses SITEID | |
567 | * @param int|string $categoryid Either the categoryid for the courses or 'all' | |
568 | * @param string $sort A SQL sort field and direction | |
569 | * @param array $fields An array of additional fields to fetch | |
570 | * @return array | |
70f15878 | 571 | */ |
572 | function get_courses_wmanagers($categoryid=0, $sort="c.sortorder ASC", $fields=array()) { | |
573 | /* | |
bfbfdb53 | 574 | * The plan is to |
70f15878 | 575 | * |
576 | * - Grab the courses JOINed w/context | |
577 | * | |
578 | * - Grab the interesting course-manager RAs | |
579 | * JOINed with a base user obj and add them to each course | |
580 | * | |
581 | * So as to do all the work in 2 DB queries. The RA+user JOIN | |
582 | * ends up being pretty expensive if it happens over _all_ | |
583 | * courses on a large site. (Are we surprised!?) | |
584 | * | |
585 | * So this should _never_ get called with 'all' on a large site. | |
586 | * | |
587 | */ | |
3b8a284c | 588 | global $USER, $CFG, $DB; |
70f15878 | 589 | |
3b8a284c | 590 | $params = array(); |
70f15878 | 591 | $allcats = false; // bool flag |
592 | if ($categoryid === 'all') { | |
593 | $categoryclause = ''; | |
594 | $allcats = true; | |
595 | } elseif (is_numeric($categoryid)) { | |
3b8a284c | 596 | $categoryclause = "c.category = :catid"; |
597 | $params['catid'] = $categoryid; | |
70f15878 | 598 | } else { |
599 | debugging("Could not recognise categoryid = $categoryid"); | |
600 | $categoryclause = ''; | |
601 | } | |
602 | ||
603 | $basefields = array('id', 'category', 'sortorder', | |
604 | 'shortname', 'fullname', 'idnumber', | |
70f15878 | 605 | 'guest', 'startdate', 'visible', |
606 | 'newsitems', 'cost', 'enrol', | |
607 | 'groupmode', 'groupmodeforce'); | |
608 | ||
609 | if (!is_null($fields) && is_string($fields)) { | |
610 | if (empty($fields)) { | |
611 | $fields = $basefields; | |
612 | } else { | |
bfbfdb53 | 613 | // turn the fields from a string to an array that |
70f15878 | 614 | // get_user_courses_bycap() will like... |
615 | $fields = explode(',',$fields); | |
616 | $fields = array_map('trim', $fields); | |
617 | $fields = array_unique(array_merge($basefields, $fields)); | |
618 | } | |
619 | } elseif (is_array($fields)) { | |
620 | $fields = array_merge($basefields,$fields); | |
621 | } | |
622 | $coursefields = 'c.' .join(',c.', $fields); | |
623 | ||
624 | if (empty($sort)) { | |
625 | $sortstatement = ""; | |
626 | } else { | |
627 | $sortstatement = "ORDER BY $sort"; | |
628 | } | |
629 | ||
e89f157b | 630 | $where = 'WHERE c.id != ' . SITEID; |
70f15878 | 631 | if ($categoryclause !== ''){ |
e89f157b | 632 | $where = "$where AND $categoryclause"; |
70f15878 | 633 | } |
634 | ||
635 | // pull out all courses matching the cat | |
4f0c2d00 PS |
636 | list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); |
637 | $sql = "SELECT $coursefields $ccselect | |
3b8a284c | 638 | FROM {course} c |
4f0c2d00 | 639 | $ccjoin |
3b8a284c | 640 | $where |
641 | $sortstatement"; | |
70f15878 | 642 | |
643 | $catpaths = array(); | |
644 | $catpath = NULL; | |
3b8a284c | 645 | if ($courses = $DB->get_records_sql($sql, $params)) { |
70f15878 | 646 | // loop on courses materialising |
bfbfdb53 | 647 | // the context, and prepping data to fetch the |
70f15878 | 648 | // managers efficiently later... |
649 | foreach ($courses as $k => $course) { | |
4f0c2d00 PS |
650 | context_instance_preload($course); |
651 | $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); | |
652 | $courses[$k] = $course; | |
70f15878 | 653 | $courses[$k]->managers = array(); |
654 | if ($allcats === false) { | |
655 | // single cat, so take just the first one... | |
656 | if ($catpath === NULL) { | |
4f0c2d00 | 657 | $catpath = preg_replace(':/\d+$:', '', $coursecontext->path); |
70f15878 | 658 | } |
659 | } else { | |
660 | // chop off the contextid of the course itself | |
661 | // like dirname() does... | |
4f0c2d00 | 662 | $catpaths[] = preg_replace(':/\d+$:', '', $coursecontext->path); |
70f15878 | 663 | } |
664 | } | |
665 | } else { | |
666 | return array(); // no courses! | |
667 | } | |
668 | ||
b1cff118 | 669 | $CFG->coursemanager = trim($CFG->coursemanager); |
670 | if (empty($CFG->coursemanager)) { | |
671 | return $courses; | |
672 | } | |
673 | ||
70f15878 | 674 | $managerroles = split(',', $CFG->coursemanager); |
675 | $catctxids = ''; | |
676 | if (count($managerroles)) { | |
677 | if ($allcats === true) { | |
678 | $catpaths = array_unique($catpaths); | |
679 | $ctxids = array(); | |
680 | foreach ($catpaths as $cpath) { | |
681 | $ctxids = array_merge($ctxids, explode('/',substr($cpath,1))); | |
682 | } | |
683 | $ctxids = array_unique($ctxids); | |
684 | $catctxids = implode( ',' , $ctxids); | |
c7a71127 | 685 | unset($catpaths); |
686 | unset($cpath); | |
70f15878 | 687 | } else { |
688 | // take the ctx path from the first course | |
689 | // as all categories will be the same... | |
690 | $catpath = substr($catpath,1); | |
691 | $catpath = preg_replace(':/\d+$:','',$catpath); | |
692 | $catctxids = str_replace('/',',',$catpath); | |
693 | } | |
694 | if ($categoryclause !== '') { | |
695 | $categoryclause = "AND $categoryclause"; | |
696 | } | |
697 | /* | |
bfbfdb53 | 698 | * Note: Here we use a LEFT OUTER JOIN that can |
70f15878 | 699 | * "optionally" match to avoid passing a ton of context |
700 | * ids in an IN() clause. Perhaps a subselect is faster. | |
701 | * | |
702 | * In any case, this SQL is not-so-nice over large sets of | |
703 | * courses with no $categoryclause. | |
704 | * | |
705 | */ | |
706 | $sql = "SELECT ctx.path, ctx.instanceid, ctx.contextlevel, | |
70f15878 | 707 | r.id AS roleid, r.name as rolename, |
708 | u.id AS userid, u.firstname, u.lastname | |
3b8a284c | 709 | FROM {role_assignments} ra |
710 | JOIN {context} ctx ON ra.contextid = ctx.id | |
711 | JOIN {user} u ON ra.userid = u.id | |
712 | JOIN {role} r ON ra.roleid = r.id | |
713 | LEFT OUTER JOIN {course} c | |
714 | ON (ctx.instanceid=c.id AND ctx.contextlevel=".CONTEXT_COURSE.") | |
c7a71127 | 715 | WHERE ( c.id IS NOT NULL"; |
716 | // under certain conditions, $catctxids is NULL | |
717 | if($catctxids == NULL){ | |
718 | $sql .= ") "; | |
719 | }else{ | |
720 | $sql .= " OR ra.contextid IN ($catctxids) )"; | |
721 | } | |
722 | ||
723 | $sql .= "AND ra.roleid IN ({$CFG->coursemanager}) | |
70f15878 | 724 | $categoryclause |
725 | ORDER BY r.sortorder ASC, ctx.contextlevel ASC, ra.sortorder ASC"; | |
3b8a284c | 726 | $rs = $DB->get_recordset_sql($sql, $params); |
bfbfdb53 | 727 | |
70f15878 | 728 | // This loop is fairly stupid as it stands - might get better |
729 | // results doing an initial pass clustering RAs by path. | |
3b8a284c | 730 | foreach($rs as $ra) { |
4f0c2d00 | 731 | $user = new stdClass; |
03cedd62 | 732 | $user->id = $ra->userid; unset($ra->userid); |
733 | $user->firstname = $ra->firstname; unset($ra->firstname); | |
734 | $user->lastname = $ra->lastname; unset($ra->lastname); | |
735 | $ra->user = $user; | |
736 | if ($ra->contextlevel == CONTEXT_SYSTEM) { | |
737 | foreach ($courses as $k => $course) { | |
738 | $courses[$k]->managers[] = $ra; | |
739 | } | |
4f0c2d00 | 740 | } else if ($ra->contextlevel == CONTEXT_COURSECAT) { |
03cedd62 | 741 | if ($allcats === false) { |
742 | // It always applies | |
70f15878 | 743 | foreach ($courses as $k => $course) { |
744 | $courses[$k]->managers[] = $ra; | |
745 | } | |
03cedd62 | 746 | } else { |
747 | foreach ($courses as $k => $course) { | |
4f0c2d00 | 748 | $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); |
03cedd62 | 749 | // Note that strpos() returns 0 as "matched at pos 0" |
4f0c2d00 | 750 | if (strpos($coursecontext->path, $ra->path.'/') === 0) { |
03cedd62 | 751 | // Only add it to subpaths |
70f15878 | 752 | $courses[$k]->managers[] = $ra; |
753 | } | |
70f15878 | 754 | } |
70f15878 | 755 | } |
03cedd62 | 756 | } else { // course-level |
4f0c2d00 | 757 | if (!array_key_exists($ra->instanceid, $courses)) { |
03cedd62 | 758 | //this course is not in a list, probably a frontpage course |
759 | continue; | |
760 | } | |
761 | $courses[$ra->instanceid]->managers[] = $ra; | |
70f15878 | 762 | } |
763 | } | |
3b8a284c | 764 | $rs->close(); |
70f15878 | 765 | } |
766 | ||
767 | return $courses; | |
768 | } | |
02ebf404 | 769 | |
18a97fd8 | 770 | /** |
bfbfdb53 | 771 | * Convenience function - lists courses that a user has access to view. |
fbc21ae8 | 772 | * |
82c62d1b | 773 | * For admins and others with access to "every" course in the system, we should |
774 | * try to get courses with explicit RAs. | |
775 | * | |
776 | * NOTE: this function is heavily geared towards the perspective of the user | |
bfbfdb53 | 777 | * passed in $userid. So it will hide courses that the user cannot see |
82c62d1b | 778 | * (for any reason) even if called from cron or from another $USER's |
779 | * perspective. | |
bfbfdb53 | 780 | * |
82c62d1b | 781 | * If you really want to know what courses are assigned to the user, |
bfbfdb53 | 782 | * without any hiding or scheming, call the lower-level |
82c62d1b | 783 | * get_user_courses_bycap(). |
784 | * | |
785 | * | |
786 | * Notes inherited from get_user_courses_bycap(): | |
e1d5e5c1 | 787 | * |
788 | * - $fields is an array of fieldnames to ADD | |
789 | * so name the fields you really need, which will | |
790 | * be added and uniq'd | |
791 | * | |
792 | * - the course records have $c->context which is a fully | |
793 | * valid context object. Saves you a query per course! | |
794 | * | |
3564771d | 795 | * @global object |
796 | * @global object | |
797 | * @global object | |
798 | * @uses CONTEXT_SYSTEM | |
799 | * @uses CONTEXT_COURSE | |
800 | * @uses CONTEXT_COURSECAT | |
7290c7fa | 801 | * @param int $userid The user of interest |
33f85740 | 802 | * @param string $sort the sortorder in the course table |
3564771d | 803 | * @param array $fields names of _additional_ fields to return (also accepts a string) |
f8e1c7af | 804 | * @param bool $doanything True if using the doanything flag |
805 | * @param int $limit Maximum number of records to return, or 0 for unlimited | |
3564771d | 806 | * @return array Array of {@link $COURSE} of course objects |
fbc21ae8 | 807 | */ |
e1d5e5c1 | 808 | function get_my_courses($userid, $sort='visible DESC,sortorder ASC', $fields=NULL, $doanything=false,$limit=0) { |
3b8a284c | 809 | global $CFG, $USER, $DB; |
5930cded | 810 | |
4f0c2d00 PS |
811 | // Guest account does not have any courses |
812 | if (isguestuser()) { | |
4dbca99e | 813 | return(array()); |
814 | } | |
601edb90 | 815 | |
352f6f74 | 816 | $basefields = array('id', 'category', 'sortorder', |
817 | 'shortname', 'fullname', 'idnumber', | |
352f6f74 | 818 | 'guest', 'startdate', 'visible', |
819 | 'newsitems', 'cost', 'enrol', | |
820 | 'groupmode', 'groupmodeforce'); | |
821 | ||
e1d5e5c1 | 822 | if (!is_null($fields) && is_string($fields)) { |
823 | if (empty($fields)) { | |
352f6f74 | 824 | $fields = $basefields; |
e1d5e5c1 | 825 | } else { |
bfbfdb53 | 826 | // turn the fields from a string to an array that |
573674bf | 827 | // get_user_courses_bycap() will like... |
352f6f74 | 828 | $fields = explode(',',$fields); |
829 | $fields = array_map('trim', $fields); | |
830 | $fields = array_unique(array_merge($basefields, $fields)); | |
831 | } | |
bbfed0ec | 832 | } elseif (is_array($fields)) { |
bfbfdb53 | 833 | $fields = array_unique(array_merge($basefields, $fields)); |
352f6f74 | 834 | } else { |
835 | $fields = $basefields; | |
836 | } | |
837 | ||
b9e9491a | 838 | $orderby = ''; |
839 | $sort = trim($sort); | |
840 | if (!empty($sort)) { | |
70070493 | 841 | $rawsorts = explode(',', $sort); |
842 | $sorts = array(); | |
843 | foreach ($rawsorts as $rawsort) { | |
844 | $rawsort = trim($rawsort); | |
c7e6b7e4 | 845 | if (strpos($rawsort, 'c.') === 0) { |
70070493 | 846 | $rawsort = substr($rawsort, 2); |
847 | } | |
848 | $sorts[] = trim($rawsort); | |
849 | } | |
850 | $sort = 'c.'.implode(',c.', $sorts); | |
b9e9491a | 851 | $orderby = "ORDER BY $sort"; |
852 | } | |
853 | ||
352f6f74 | 854 | // |
855 | // Logged-in user - Check cached courses | |
856 | // | |
857 | // NOTE! it's a _string_ because | |
858 | // - it's all we'll ever use | |
859 | // - it serialises much more compact than an array | |
82c62d1b | 860 | // this a big concern here - cost of serialise |
861 | // and unserialise gets huge as the session grows | |
352f6f74 | 862 | // |
863 | // If the courses are too many - it won't be set | |
864 | // for large numbers of courses, caching in the session | |
865 | // has marginal benefits (costs too much, not | |
866 | // worthwhile...) and we may hit SQL parser limits | |
867 | // because we use IN() | |
868 | // | |
ae1555ae | 869 | if ($userid === $USER->id) { |
bfbfdb53 | 870 | if (isset($USER->loginascontext) |
fe3141e0 | 871 | && $USER->loginascontext->contextlevel == CONTEXT_COURSE) { |
ae1555ae | 872 | // list _only_ this course |
873 | // anything else is asking for trouble... | |
874 | $courseids = $USER->loginascontext->instanceid; | |
bfbfdb53 | 875 | } elseif (isset($USER->mycourses) |
ae1555ae | 876 | && is_string($USER->mycourses)) { |
877 | if ($USER->mycourses === '') { | |
878 | // empty str means: user has no courses | |
879 | // ... so do the easy thing... | |
880 | return array(); | |
881 | } else { | |
882 | $courseids = $USER->mycourses; | |
883 | } | |
884 | } | |
885 | if (isset($courseids)) { | |
bfbfdb53 | 886 | // The data massaging here MUST be kept in sync with |
352f6f74 | 887 | // get_user_courses_bycap() so we return |
888 | // the same... | |
889 | // (but here we don't need to check has_cap) | |
890 | $coursefields = 'c.' .join(',c.', $fields); | |
4f0c2d00 PS |
891 | list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); |
892 | $sql = "SELECT $coursefields $ccselect, cc.path AS categorypath | |
3b8a284c | 893 | FROM {course} c |
894 | JOIN {course_categories} cc ON c.category=cc.id | |
4f0c2d00 | 895 | $ccjoin |
3b8a284c | 896 | WHERE c.id IN ($courseids) |
897 | $orderby"; | |
898 | $rs = $DB->get_recordset_sql($sql); | |
352f6f74 | 899 | $courses = array(); |
900 | $cc = 0; // keep count | |
3b8a284c | 901 | foreach ($rs as $c) { |
03cedd62 | 902 | // build the context obj |
4f0c2d00 | 903 | context_instance_preload($c); |
c1b7a5e5 | 904 | |
4a09658e | 905 | if ($limit > 0 && $cc >= $limit) { |
03cedd62 | 906 | break; |
352f6f74 | 907 | } |
117bd748 | 908 | |
4a09658e | 909 | $courses[$c->id] = $c; |
910 | $cc++; | |
352f6f74 | 911 | } |
3b8a284c | 912 | $rs->close(); |
352f6f74 | 913 | return $courses; |
2f3499b7 | 914 | } |
915 | } | |
152a9060 | 916 | |
352f6f74 | 917 | // Non-cached - get accessinfo |
e1d5e5c1 | 918 | if ($userid === $USER->id && isset($USER->access)) { |
aeb3916b | 919 | $accessinfo = $USER->access; |
bdf3bbd1 | 920 | } else { |
e1d5e5c1 | 921 | $accessinfo = get_user_access_sitewide($userid); |
aeb3916b | 922 | } |
352f6f74 | 923 | |
bfbfdb53 | 924 | |
4f0c2d00 | 925 | $courses = get_user_courses_bycap($userid, 'moodle/course:participate', $accessinfo, |
573674bf | 926 | $doanything, $sort, $fields, |
927 | $limit); | |
352f6f74 | 928 | |
82c62d1b | 929 | $cats = NULL; |
930 | // If we have to walk category visibility | |
931 | // to eval course visibility, get the categories | |
932 | if (empty($CFG->allowvisiblecoursesinhiddencategories)) { | |
4f0c2d00 PS |
933 | list($ccselect, $ccjoin) = context_instance_preload_sql('cc.id', CONTEXT_COURSECAT, 'ctx'); |
934 | $sql = "SELECT cc.id, cc.path, cc.visible $ccselect | |
3b8a284c | 935 | FROM {course_categories} cc |
4f0c2d00 | 936 | $ccjoin |
3b8a284c | 937 | ORDER BY cc.id"; |
938 | $rs = $DB->get_recordset_sql($sql); | |
bfbfdb53 | 939 | |
940 | // Using a temporary array instead of $cats here, to avoid a "true" result when isnull($cats) further down | |
941 | $categories = array(); | |
3b8a284c | 942 | foreach($rs as $course_cat) { |
03cedd62 | 943 | // build the context obj |
4f0c2d00 | 944 | context_instance_preload($course_cat); |
03cedd62 | 945 | $categories[$course_cat->id] = $course_cat; |
82c62d1b | 946 | } |
3b8a284c | 947 | $rs->close(); |
bfbfdb53 | 948 | |
949 | if (!empty($categories)) { | |
950 | $cats = $categories; | |
951 | } | |
952 | ||
953 | unset($course_cat); | |
82c62d1b | 954 | } |
352f6f74 | 955 | // |
956 | // Strangely, get_my_courses() is expected to return the | |
aeb3916b | 957 | // array keyed on id, which messes up the sorting |
352f6f74 | 958 | // So do that, and also cache the ids in the session if appropriate |
959 | // | |
aeb3916b | 960 | $kcourses = array(); |
bfbfdb53 | 961 | $courses_count = count($courses); |
352f6f74 | 962 | $cacheids = NULL; |
82c62d1b | 963 | $vcatpaths = array(); |
bfbfdb53 | 964 | if ($userid === $USER->id && $courses_count < 500) { |
352f6f74 | 965 | $cacheids = array(); |
966 | } | |
bfbfdb53 | 967 | for ($n=0; $n<$courses_count; $n++) { |
82c62d1b | 968 | |
969 | // | |
b00cb46b | 970 | // Check whether $USER (not $userid) can _actually_ see them |
82c62d1b | 971 | // Easy if $CFG->allowvisiblecoursesinhiddencategories |
972 | // is set, and we don't have to care about categories. | |
973 | // Lots of work otherwise... (all in mem though!) | |
974 | // | |
bfbfdb53 | 975 | $cansee = false; |
82c62d1b | 976 | if (is_null($cats)) { // easy rules! |
977 | if ($courses[$n]->visible == true) { | |
978 | $cansee = true; | |
979 | } elseif (has_capability('moodle/course:viewhiddencourses', | |
b00cb46b | 980 | $courses[$n]->context, $USER->id)) { |
82c62d1b | 981 | $cansee = true; |
982 | } | |
983 | } else { | |
984 | // | |
985 | // Is the cat visible? | |
986 | // we have to assume it _is_ visible | |
987 | // so we can shortcut when we find a hidden one | |
988 | // | |
989 | $viscat = true; | |
990 | $cpath = $courses[$n]->categorypath; | |
991 | if (isset($vcatpaths[$cpath])) { | |
992 | $viscat = $vcatpaths[$cpath]; | |
993 | } else { | |
994 | $cpath = substr($cpath,1); // kill leading slash | |
995 | $cpath = explode('/',$cpath); | |
996 | $ccct = count($cpath); | |
997 | for ($m=0;$m<$ccct;$m++) { | |
998 | $ccid = $cpath[$m]; | |
999 | if ($cats[$ccid]->visible==false) { | |
1000 | $viscat = false; | |
1001 | break; | |
1002 | } | |
1003 | } | |
1004 | $vcatpaths[$courses[$n]->categorypath] = $viscat; | |
1005 | } | |
1006 | ||
1007 | // | |
b00cb46b | 1008 | // Perhaps it's actually visible to $USER |
8ed5dd63 | 1009 | // check moodle/category:viewhiddencategories |
bfbfdb53 | 1010 | // |
82c62d1b | 1011 | // The name isn't obvious, but the description says |
1012 | // "See hidden categories" so the user shall see... | |
bfbfdb53 | 1013 | // But also check if the allowvisiblecoursesinhiddencategories setting is true, and check for course visibility |
82c62d1b | 1014 | if ($viscat === false) { |
bfbfdb53 | 1015 | $catctx = $cats[$courses[$n]->category]->context; |
8ed5dd63 | 1016 | if (has_capability('moodle/category:viewhiddencategories', $catctx, $USER->id)) { |
82c62d1b | 1017 | $vcatpaths[$courses[$n]->categorypath] = true; |
1018 | $viscat = true; | |
bfbfdb53 | 1019 | } elseif ($CFG->allowvisiblecoursesinhiddencategories && $courses[$n]->visible == true) { |
1020 | $viscat = true; | |
82c62d1b | 1021 | } |
1022 | } | |
1023 | ||
1024 | // | |
1025 | // Decision matrix | |
1026 | // | |
1027 | if ($viscat === true) { | |
1028 | if ($courses[$n]->visible == true) { | |
1029 | $cansee = true; | |
1030 | } elseif (has_capability('moodle/course:viewhiddencourses', | |
b00cb46b | 1031 | $courses[$n]->context, $USER->id)) { |
82c62d1b | 1032 | $cansee = true; |
1033 | } | |
1034 | } | |
1035 | } | |
1036 | if ($cansee === true) { | |
1037 | $kcourses[$courses[$n]->id] = $courses[$n]; | |
1038 | if (is_array($cacheids)) { | |
1039 | $cacheids[] = $courses[$n]->id; | |
1040 | } | |
352f6f74 | 1041 | } |
1042 | } | |
1043 | if (is_array($cacheids)) { | |
1044 | // Only happens | |
1045 | // - for the logged in user | |
1046 | // - below the threshold (500) | |
1047 | // empty string is _valid_ | |
1048 | $USER->mycourses = join(',',$cacheids); | |
1049 | } elseif ($userid === $USER->id && isset($USER->mycourses)) { | |
1050 | // cheap sanity check | |
1051 | unset($USER->mycourses); | |
aeb3916b | 1052 | } |
352f6f74 | 1053 | |
aeb3916b | 1054 | return $kcourses; |
02ebf404 | 1055 | } |
1056 | ||
18a97fd8 | 1057 | /** |
7290c7fa | 1058 | * A list of courses that match a search |
fbc21ae8 | 1059 | * |
3564771d | 1060 | * @global object |
1061 | * @global object | |
1062 | * @param array $searchterms An array of search criteria | |
1063 | * @param string $sort A field and direction to sort by | |
1064 | * @param int $page The page number to get | |
1065 | * @param int $recordsperpage The number of records per page | |
1066 | * @param int $totalcount Passed in by reference. | |
7290c7fa | 1067 | * @return object {@link $COURSE} records |
fbc21ae8 | 1068 | */ |
d4419d55 | 1069 | function get_courses_search($searchterms, $sort='fullname ASC', $page=0, $recordsperpage=50, &$totalcount) { |
3b8a284c | 1070 | global $CFG, $DB; |
02ebf404 | 1071 | |
06c1a1da | 1072 | if ($DB->sql_regex_supported()) { |
1073 | $REGEXP = $DB->sql_regex(true); | |
1074 | $NOTREGEXP = $DB->sql_regex(false); | |
02ebf404 | 1075 | } |
3b8a284c | 1076 | $LIKE = $DB->sql_ilike(); // case-insensitive |
02ebf404 | 1077 | |
06c1a1da | 1078 | $searchcond = array(); |
1079 | $params = array(); | |
1080 | $i = 0; | |
02ebf404 | 1081 | |
06c1a1da | 1082 | $concat = $DB->sql_concat('c.summary', "' '", 'c.fullname'); |
3b8a284c | 1083 | |
02ebf404 | 1084 | foreach ($searchterms as $searchterm) { |
06c1a1da | 1085 | $i++; |
6bb0f67f | 1086 | |
0f62a5b5 | 1087 | $NOT = ''; /// Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle |
1088 | /// will use it to simulate the "-" operator with LIKE clause | |
1089 | ||
6bb0f67f | 1090 | /// Under Oracle and MSSQL, trim the + and - operators and perform |
0f62a5b5 | 1091 | /// simpler LIKE (or NOT LIKE) queries |
06c1a1da | 1092 | if (!$DB->sql_regex_supported()) { |
0f62a5b5 | 1093 | if (substr($searchterm, 0, 1) == '-') { |
1094 | $NOT = ' NOT '; | |
1095 | } | |
6bb0f67f | 1096 | $searchterm = trim($searchterm, '+-'); |
1097 | } | |
1098 | ||
06c1a1da | 1099 | // TODO: +- may not work for non latin languages |
3b8a284c | 1100 | |
d4419d55 | 1101 | if (substr($searchterm,0,1) == '+') { |
06c1a1da | 1102 | $searchterm = trim($searchterm, '+-'); |
1103 | $searchterm = preg_quote($searchterm, '|'); | |
1104 | $searchcond[] = "$concat $REGEXP :ss$i"; | |
1105 | $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)"; | |
1106 | ||
a8b56716 | 1107 | } else if (substr($searchterm,0,1) == "-") { |
06c1a1da | 1108 | $searchterm = trim($searchterm, '+-'); |
1109 | $searchterm = preg_quote($searchterm, '|'); | |
1110 | $searchcond[] = "$concat $NOTREGEXP :ss$i"; | |
1111 | $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)"; | |
1112 | ||
a8b56716 | 1113 | } else { |
06c1a1da | 1114 | $searchcond[] = "$concat $NOT $LIKE :ss$i"; |
1115 | $params['ss'.$i] = "%$searchterm%"; | |
a8b56716 | 1116 | } |
02ebf404 | 1117 | } |
1118 | ||
06c1a1da | 1119 | if (empty($searchcond)) { |
1120 | $totalcount = 0; | |
1121 | return array(); | |
1122 | } | |
1123 | ||
1124 | $searchcond = implode(" AND ", $searchcond); | |
1125 | ||
4f0c2d00 PS |
1126 | list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx'); |
1127 | $sql = "SELECT c.* $ccselect | |
3b8a284c | 1128 | FROM {course} c |
4f0c2d00 | 1129 | $ccjoin |
06c1a1da | 1130 | WHERE $searchcond AND c.id <> ".SITEID." |
1131 | ORDER BY $sort"; | |
2c64f65c | 1132 | $courses = array(); |
3b8a284c | 1133 | $c = 0; // counts how many visible courses we've seen |
02ebf404 | 1134 | |
3b8a284c | 1135 | if ($rs = $DB->get_recordset_sql($sql, $params)) { |
2c64f65c | 1136 | // Tiki pagination |
1137 | $limitfrom = $page * $recordsperpage; | |
1138 | $limitto = $limitfrom + $recordsperpage; | |
2c64f65c | 1139 | |
3b8a284c | 1140 | foreach($rs as $course) { |
4f0c2d00 PS |
1141 | context_instance_preload($course); |
1142 | $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); | |
1143 | if ($course->visible || has_capability('moodle/course:viewhiddencourses', $coursecontext)) { | |
2c64f65c | 1144 | // Don't exit this loop till the end |
1145 | // we need to count all the visible courses | |
1146 | // to update $totalcount | |
1147 | if ($c >= $limitfrom && $c < $limitto) { | |
3b8a284c | 1148 | $courses[$course->id] = $course; |
02ebf404 | 1149 | } |
2c64f65c | 1150 | $c++; |
02ebf404 | 1151 | } |
1152 | } | |
3b8a284c | 1153 | $rs->close(); |
02ebf404 | 1154 | } |
1155 | ||
2c64f65c | 1156 | // our caller expects 2 bits of data - our return |
1157 | // array, and an updated $totalcount | |
1158 | $totalcount = $c; | |
02ebf404 | 1159 | return $courses; |
1160 | } | |
1161 | ||
1162 | ||
18a97fd8 | 1163 | /** |
40fb8aa6 | 1164 | * Returns a sorted list of categories. Each category object has a context |
1165 | * property that is a context object. | |
bfbfdb53 | 1166 | * |
40fb8aa6 | 1167 | * When asking for $parent='none' it will return all the categories, regardless |
1168 | * of depth. Wheen asking for a specific parent, the default is to return | |
1169 | * a "shallow" resultset. Pass false to $shallow and it will return all | |
bfbfdb53 | 1170 | * the child categories as well. |
1171 | * | |
3564771d | 1172 | * @global object |
1173 | * @uses CONTEXT_COURSECAT | |
613bbd7c | 1174 | * @param string $parent The parent category if any |
1175 | * @param string $sort the sortorder | |
40fb8aa6 | 1176 | * @param bool $shallow - set to false to get the children too |
613bbd7c | 1177 | * @return array of categories |
fbc21ae8 | 1178 | */ |
40fb8aa6 | 1179 | function get_categories($parent='none', $sort=NULL, $shallow=true) { |
3b8a284c | 1180 | global $DB; |
40fb8aa6 | 1181 | |
1182 | if ($sort === NULL) { | |
1183 | $sort = 'ORDER BY cc.sortorder ASC'; | |
1184 | } elseif ($sort ==='') { | |
1185 | // leave it as empty | |
1186 | } else { | |
1187 | $sort = "ORDER BY $sort"; | |
1188 | } | |
02ebf404 | 1189 | |
4f0c2d00 PS |
1190 | list($ccselect, $ccjoin) = context_instance_preload_sql('cc.id', CONTEXT_COURSECAT, 'ctx'); |
1191 | ||
814748c9 | 1192 | if ($parent === 'none') { |
4f0c2d00 | 1193 | $sql = "SELECT cc.* $ccselect |
3b8a284c | 1194 | FROM {course_categories} cc |
4f0c2d00 | 1195 | $ccjoin |
40fb8aa6 | 1196 | $sort"; |
3b8a284c | 1197 | $params = array(); |
1198 | ||
40fb8aa6 | 1199 | } elseif ($shallow) { |
4f0c2d00 | 1200 | $sql = "SELECT cc.* $ccselect |
3b8a284c | 1201 | FROM {course_categories} cc |
4f0c2d00 | 1202 | $ccjoin |
3b8a284c | 1203 | WHERE cc.parent=? |
40fb8aa6 | 1204 | $sort"; |
3b8a284c | 1205 | $params = array($parent); |
1206 | ||
02ebf404 | 1207 | } else { |
4f0c2d00 | 1208 | $sql = "SELECT cc.* $ccselect |
3b8a284c | 1209 | FROM {course_categories} cc |
4f0c2d00 | 1210 | $ccjoin |
3b8a284c | 1211 | JOIN {course_categories} ccp |
1212 | ON (cc.path LIKE ".$DB->sql_concat('ccp.path',"'%'").") | |
1213 | WHERE ccp.id=? | |
40fb8aa6 | 1214 | $sort"; |
3b8a284c | 1215 | $params = array($parent); |
02ebf404 | 1216 | } |
40fb8aa6 | 1217 | $categories = array(); |
1218 | ||
3b8a284c | 1219 | if( $rs = $DB->get_recordset_sql($sql, $params) ){ |
1220 | foreach($rs as $cat) { | |
4f0c2d00 PS |
1221 | context_instance_preload($cat); |
1222 | $catcontext = get_context_instance(CONTEXT_COURSECAT, $cat->id); | |
1223 | if ($cat->visible || has_capability('moodle/category:viewhiddencategories', $catcontext)) { | |
40fb8aa6 | 1224 | $categories[$cat->id] = $cat; |
02ebf404 | 1225 | } |
1226 | } | |
3b8a284c | 1227 | $rs->close(); |
02ebf404 | 1228 | } |
1229 | return $categories; | |
1230 | } | |
1231 | ||
1232 | ||
2327b9df | 1233 | /** |
1234 | * Returns an array of category ids of all the subcategories for a given | |
1235 | * category. | |
3564771d | 1236 | * |
1237 | * @global object | |
1238 | * @param int $catid - The id of the category whose subcategories we want to find. | |
2327b9df | 1239 | * @return array of category ids. |
1240 | */ | |
1241 | function get_all_subcategories($catid) { | |
3b8a284c | 1242 | global $DB; |
2327b9df | 1243 | |
1244 | $subcats = array(); | |
1245 | ||
3b8a284c | 1246 | if ($categories = $DB->get_records('course_categories', array('parent'=>$catid))) { |
2327b9df | 1247 | foreach ($categories as $cat) { |
1248 | array_push($subcats, $cat->id); | |
1249 | $subcats = array_merge($subcats, get_all_subcategories($cat->id)); | |
1250 | } | |
1251 | } | |
1252 | return $subcats; | |
1253 | } | |
1254 | ||
18a97fd8 | 1255 | /** |
0cbe8111 | 1256 | * Return specified category, default if given does not exist |
117bd748 | 1257 | * |
3564771d | 1258 | * @global object |
1259 | * @uses MAX_COURSES_IN_CATEGORY | |
1260 | * @uses CONTEXT_COURSECAT | |
1261 | * @uses SYSCONTEXTID | |
0cbe8111 | 1262 | * @param int $catid course category id |
1263 | * @return object caregory | |
1264 | */ | |
1265 | function get_course_category($catid=0) { | |
1266 | global $DB; | |
1267 | ||
1268 | $category = false; | |
1269 | ||
1270 | if (!empty($catid)) { | |
1271 | $category = $DB->get_record('course_categories', array('id'=>$catid)); | |
1272 | } | |
8f0cd6ef | 1273 | |
0cbe8111 | 1274 | if (!$category) { |
1275 | // the first category is considered default for now | |
1276 | if ($category = $DB->get_records('course_categories', null, 'sortorder', '*', 0, 1)) { | |
1277 | $category = reset($category); | |
1278 | ||
1279 | } else { | |
1280 | $cat = new object(); | |
1281 | $cat->name = get_string('miscellaneous'); | |
1282 | $cat->depth = 1; | |
1283 | $cat->sortorder = MAX_COURSES_IN_CATEGORY; | |
1284 | $cat->timemodified = time(); | |
a8d6ef8c | 1285 | $catid = $DB->insert_record('course_categories', $cat); |
0cbe8111 | 1286 | // make sure category context exists |
1287 | get_context_instance(CONTEXT_COURSECAT, $catid); | |
1288 | mark_context_dirty('/'.SYSCONTEXTID); | |
7a9d505b | 1289 | fix_course_sortorder(); // Required to build course_categories.depth and .path. |
0cbe8111 | 1290 | $category = $DB->get_record('course_categories', array('id'=>$catid)); |
f41ef63e | 1291 | } |
0cbe8111 | 1292 | } |
ba87a4da | 1293 | |
0cbe8111 | 1294 | return $category; |
1295 | } | |
1296 | ||
1297 | /** | |
1298 | * Fixes course category and course sortorder, also verifies category and course parents and paths. | |
a1b892cc | 1299 | * (circular references are not fixed) |
3564771d | 1300 | * |
1301 | * @global object | |
1302 | * @global object | |
1303 | * @uses MAX_COURSES_IN_CATEGORY | |
1304 | * @uses MAX_COURSE_CATEGORIES | |
1305 | * @uses SITEID | |
1306 | * @uses CONTEXT_COURSE | |
1307 | * @return void | |
0cbe8111 | 1308 | */ |
1309 | function fix_course_sortorder() { | |
1310 | global $DB, $SITE; | |
1311 | ||
1312 | //WARNING: this is PHP5 only code! | |
1313 | ||
1314 | if ($unsorted = $DB->get_records('course_categories', array('sortorder'=>0))) { | |
1315 | //move all categories that are not sorted yet to the end | |
1316 | $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('sortorder'=>0)); | |
1317 | } | |
1318 | ||
1319 | $allcats = $DB->get_records('course_categories', null, 'sortorder, id', 'id, sortorder, parent, depth, path'); | |
1320 | $topcats = array(); | |
1321 | $brokencats = array(); | |
1322 | foreach ($allcats as $cat) { | |
1323 | $sortorder = (int)$cat->sortorder; | |
1324 | if (!$cat->parent) { | |
1325 | while(isset($topcats[$sortorder])) { | |
1326 | $sortorder++; | |
1327 | } | |
1328 | $topcats[$sortorder] = $cat; | |
1329 | continue; | |
1330 | } | |
1331 | if (!isset($allcats[$cat->parent])) { | |
1332 | $brokencats[] = $cat; | |
1333 | continue; | |
c5d13b68 | 1334 | } |
0cbe8111 | 1335 | if (!isset($allcats[$cat->parent]->children)) { |
1336 | $allcats[$cat->parent]->children = array(); | |
c5d13b68 | 1337 | } |
0cbe8111 | 1338 | while(isset($allcats[$cat->parent]->children[$sortorder])) { |
1339 | $sortorder++; | |
1340 | } | |
1341 | $allcats[$cat->parent]->children[$sortorder] = $cat; | |
f41ef63e | 1342 | } |
0cbe8111 | 1343 | unset($allcats); |
39f65595 | 1344 | |
0cbe8111 | 1345 | // add broken cats to category tree |
1346 | if ($brokencats) { | |
1347 | $defaultcat = reset($topcats); | |
1348 | foreach ($brokencats as $cat) { | |
1349 | $topcats[] = $cat; | |
a1b892cc | 1350 | } |
ba87a4da | 1351 | } |
1352 | ||
0cbe8111 | 1353 | // now walk recursively the tree and fix any problems found |
1354 | $sortorder = 0; | |
1355 | $fixcontexts = array(); | |
1356 | _fix_course_cats($topcats, $sortorder, 0, 0, '', $fixcontexts); | |
1357 | ||
1358 | // detect if there are "multiple" frontpage courses and fix them if needed | |
1359 | $frontcourses = $DB->get_records('course', array('category'=>0), 'id'); | |
1360 | if (count($frontcourses) > 1) { | |
1361 | if (isset($frontcourses[SITEID])) { | |
1362 | $frontcourse = $frontcourses[SITEID]; | |
1363 | unset($frontcourses[SITEID]); | |
1364 | } else { | |
1365 | $frontcourse = array_shift($frontcourses); | |
1366 | } | |
1367 | $defaultcat = reset($topcats); | |
1368 | foreach ($frontcourses as $course) { | |
1369 | $DB->set_field('course', 'category', $defaultcat->id, array('id'=>$course->id)); | |
1370 | $context = get_context_instance(CONTEXT_COURSE, $course->id); | |
1371 | $fixcontexts[$context->id] = $context; | |
1372 | } | |
1373 | unset($frontcourses); | |
1374 | } else { | |
1375 | $frontcourse = reset($frontcourses); | |
814748c9 | 1376 | } |
1377 | ||
0cbe8111 | 1378 | // now fix the paths and depths in context table if needed |
1379 | if ($fixcontexts) { | |
1380 | rebuild_contexts($fixcontexts); | |
39f65595 | 1381 | } |
5930cded | 1382 | |
0cbe8111 | 1383 | // release memory |
1384 | unset($topcats); | |
1385 | unset($brokencats); | |
1386 | unset($fixcontexts); | |
1387 | ||
1388 | // fix frontpage course sortorder | |
1389 | if ($frontcourse->sortorder != 1) { | |
1390 | $DB->set_field('course', 'sortorder', 1, array('id'=>$frontcourse->id)); | |
39f65595 | 1391 | } |
1392 | ||
0cbe8111 | 1393 | // now fix the course counts in category records if needed |
1394 | $sql = "SELECT cc.id, cc.coursecount, COUNT(c.id) AS newcount | |
1395 | FROM {course_categories} cc | |
1396 | LEFT JOIN {course} c ON c.category = cc.id | |
1397 | GROUP BY cc.id, cc.coursecount | |
1398 | HAVING cc.coursecount <> COUNT(c.id)"; | |
ba87a4da | 1399 | |
0cbe8111 | 1400 | if ($updatecounts = $DB->get_records_sql($sql)) { |
1401 | foreach ($updatecounts as $cat) { | |
1402 | $cat->coursecount = $cat->newcount; | |
1403 | unset($cat->newcount); | |
1404 | $DB->update_record_raw('course_categories', $cat, true); | |
a1b892cc | 1405 | } |
02ebf404 | 1406 | } |
8f0cd6ef | 1407 | |
0cbe8111 | 1408 | // now make sure that sortorders in course table are withing the category sortorder ranges |
8ed5dd63 | 1409 | $sql = "SELECT DISTINCT cc.id, cc.sortorder |
0cbe8111 | 1410 | FROM {course_categories} cc |
1411 | JOIN {course} c ON c.category = cc.id | |
1412 | WHERE c.sortorder < cc.sortorder OR c.sortorder > cc.sortorder + ".MAX_COURSES_IN_CATEGORY; | |
1413 | ||
1414 | if ($fixcategories = $DB->get_records_sql($sql)) { | |
1415 | //fix the course sortorder ranges | |
1416 | foreach ($fixcategories as $cat) { | |
1417 | $sql = "UPDATE {course} | |
78a0635c | 1418 | SET sortorder = ".$DB->sql_modulo('sortorder', MAX_COURSES_IN_CATEGORY)." + ? |
0cbe8111 | 1419 | WHERE category = ?"; |
1420 | $DB->execute($sql, array($cat->sortorder, $cat->id)); | |
1421 | } | |
814748c9 | 1422 | } |
0cbe8111 | 1423 | unset($fixcategories); |
1424 | ||
1425 | // categories having courses with sortorder duplicates or having gaps in sortorder | |
1426 | $sql = "SELECT DISTINCT c1.category AS id , cc.sortorder | |
1427 | FROM {course} c1 | |
1428 | JOIN {course} c2 ON c1.sortorder = c2.sortorder | |
1429 | JOIN {course_categories} cc ON (c1.category = cc.id) | |
1430 | WHERE c1.id <> c2.id"; | |
1431 | $fixcategories = $DB->get_records_sql($sql); | |
1432 | ||
1433 | $sql = "SELECT cc.id, cc.sortorder, cc.coursecount, MAX(c.sortorder) AS maxsort, MIN(c.sortorder) AS minsort | |
1434 | FROM {course_categories} cc | |
1435 | JOIN {course} c ON c.category = cc.id | |
1436 | GROUP BY cc.id, cc.sortorder, cc.coursecount | |
1437 | HAVING (MAX(c.sortorder) <> cc.sortorder + cc.coursecount) OR (MIN(c.sortorder) <> cc.sortorder + 1)"; | |
1438 | $gapcategories = $DB->get_records_sql($sql); | |
1439 | ||
1440 | foreach ($gapcategories as $cat) { | |
1441 | if (isset($fixcategories[$cat->id])) { | |
1442 | // duplicates detected already | |
1443 | ||
1444 | } else if ($cat->minsort == $cat->sortorder and $cat->maxsort == $cat->sortorder + $cat->coursecount - 1) { | |
1445 | // easy - new course inserted with sortorder 0, the rest is ok | |
1446 | $sql = "UPDATE {course} | |
1447 | SET sortorder = sortorder + 1 | |
1448 | WHERE category = ?"; | |
1449 | $DB->execute($sql, array($cat->id)); | |
758b9a4d | 1450 | |
0cbe8111 | 1451 | } else { |
1452 | // it needs full resorting | |
1453 | $fixcategories[$cat->id] = $cat; | |
6bc502cc | 1454 | } |
1455 | } | |
0cbe8111 | 1456 | unset($gapcategories); |
8f0cd6ef | 1457 | |
0cbe8111 | 1458 | // fix course sortorders in problematic categories only |
1459 | foreach ($fixcategories as $cat) { | |
1460 | $i = 1; | |
1461 | $courses = $DB->get_records('course', array('category'=>$cat->id), 'sortorder ASC, id DESC', 'id, sortorder'); | |
1462 | foreach ($courses as $course) { | |
1463 | if ($course->sortorder != $cat->sortorder + $i) { | |
1464 | $course->sortorder = $cat->sortorder + $i; | |
a1b892cc | 1465 | $DB->update_record_raw('course', $course, true); |
0cbe8111 | 1466 | } |
1467 | $i++; | |
1468 | } | |
1469 | } | |
02ebf404 | 1470 | } |
1471 | ||
d8634192 | 1472 | /** |
0cbe8111 | 1473 | * Internal recursive category verification function, do not use directly! |
3564771d | 1474 | * |
1475 | * @todo Document the arguments of this function better | |
1476 | * | |
1477 | * @global object | |
1478 | * @uses MAX_COURSES_IN_CATEGORY | |
1479 | * @uses CONTEXT_COURSECAT | |
1480 | * @param array $children | |
1481 | * @param int $sortorder | |
1482 | * @param string $parent | |
1483 | * @param int $depth | |
1484 | * @param string $path | |
1485 | * @param array $fixcontexts | |
1486 | * @return void | |
0cbe8111 | 1487 | */ |
1488 | function _fix_course_cats($children, &$sortorder, $parent, $depth, $path, &$fixcontexts) { | |
c3df0901 | 1489 | global $DB; |
d8634192 | 1490 | |
0cbe8111 | 1491 | $depth++; |
c3df0901 | 1492 | |
0cbe8111 | 1493 | foreach ($children as $cat) { |
1494 | $sortorder = $sortorder + MAX_COURSES_IN_CATEGORY; | |
1495 | $update = false; | |
1496 | if ($parent != $cat->parent or $depth != $cat->depth or $path.'/'.$cat->id != $cat->path) { | |
1497 | $cat->parent = $parent; | |
1498 | $cat->depth = $depth; | |
1499 | $cat->path = $path.'/'.$cat->id; | |
1500 | $update = true; | |
c3df0901 | 1501 | |
0cbe8111 | 1502 | // make sure context caches are rebuild and dirty contexts marked |
1503 | $context = get_context_instance(CONTEXT_COURSECAT, $cat->id); | |
1504 | $fixcontexts[$context->id] = $context; | |
1505 | } | |
1506 | if ($cat->sortorder != $sortorder) { | |
1507 | $cat->sortorder = $sortorder; | |
1508 | $update = true; | |
1509 | } | |
1510 | if ($update) { | |
1511 | $DB->update_record('course_categories', $cat, true); | |
1512 | } | |
1513 | if (isset($cat->children)) { | |
1514 | _fix_course_cats($cat->children, $sortorder, $cat->id, $cat->depth, $cat->path, $fixcontexts); | |
d8634192 | 1515 | } |
1516 | } | |
1517 | } | |
1518 | ||
db4b12eb | 1519 | /** |
1520 | * List of remote courses that a user has access to via MNET. | |
1521 | * Works only on the IDP | |
1522 | * | |
3564771d | 1523 | * @global object |
1524 | * @global object | |
1525 | * @param int @userid The user id to get remote courses for | |
1526 | * @return array Array of {@link $COURSE} of course objects | |
db4b12eb | 1527 | */ |
1528 | function get_my_remotecourses($userid=0) { | |
c3df0901 | 1529 | global $DB, $USER; |
db4b12eb | 1530 | |
1531 | if (empty($userid)) { | |
1532 | $userid = $USER->id; | |
1533 | } | |
1534 | ||
f76a259e | 1535 | $sql = "SELECT c.id, c.remoteid, c.shortname, c.fullname, |
86dd62a7 | 1536 | c.hostid, c.summary, c.cat_name, |
1537 | h.name AS hostname | |
c3df0901 | 1538 | FROM {mnet_enrol_course} c |
1539 | JOIN {mnet_enrol_assignments} a ON c.id=a.courseid | |
1540 | JOIN {mnet_host} h ON c.hostid=h.id | |
1541 | WHERE a.userid=?"; | |
db4b12eb | 1542 | |
c3df0901 | 1543 | return $DB->get_records_sql($sql, array($userid)); |
db4b12eb | 1544 | } |
1545 | ||
1546 | /** | |
1547 | * List of remote hosts that a user has access to via MNET. | |
1548 | * Works on the SP | |
1549 | * | |
3564771d | 1550 | * @global object |
1551 | * @global object | |
1552 | * @return array|bool Array of host objects or false | |
db4b12eb | 1553 | */ |
1554 | function get_my_remotehosts() { | |
1555 | global $CFG, $USER; | |
1556 | ||
1557 | if ($USER->mnethostid == $CFG->mnet_localhost_id) { | |
1558 | return false; // Return nothing on the IDP | |
1559 | } | |
1560 | if (!empty($USER->mnet_foreign_host_array) && is_array($USER->mnet_foreign_host_array)) { | |
1561 | return $USER->mnet_foreign_host_array; | |
1562 | } | |
1563 | return false; | |
1564 | } | |
fbc21ae8 | 1565 | |
18a97fd8 | 1566 | /** |
fbc21ae8 | 1567 | * This function creates a default separated/connected scale |
1568 | * | |
1569 | * This function creates a default separated/connected scale | |
1570 | * so there's something in the database. The locations of | |
1571 | * strings and files is a bit odd, but this is because we | |
1572 | * need to maintain backward compatibility with many different | |
1573 | * existing language translations and older sites. | |
3564771d | 1574 | * |
1575 | * @global object | |
1576 | * @global object | |
1577 | * @return void | |
fbc21ae8 | 1578 | */ |
02ebf404 | 1579 | function make_default_scale() { |
c3df0901 | 1580 | global $CFG, $DB; |
02ebf404 | 1581 | |
1582 | $defaultscale = NULL; | |
1583 | $defaultscale->courseid = 0; | |
1584 | $defaultscale->userid = 0; | |
d4419d55 | 1585 | $defaultscale->name = get_string('separateandconnected'); |
1586 | $defaultscale->scale = get_string('postrating1', 'forum').','. | |
1587 | get_string('postrating2', 'forum').','. | |
1588 | get_string('postrating3', 'forum'); | |
02ebf404 | 1589 | $defaultscale->timemodified = time(); |
1590 | ||
8f0cd6ef | 1591 | /// Read in the big description from the file. Note this is not |
02ebf404 | 1592 | /// HTML (despite the file extension) but Moodle format text. |
c9042db5 | 1593 | $parentlang = get_parent_language(); |
ee6e91d4 | 1594 | if (is_readable($CFG->dataroot .'/lang/'. $CFG->lang .'/help/forum/ratings.html')) { |
1595 | $file = file($CFG->dataroot .'/lang/'. $CFG->lang .'/help/forum/ratings.html'); | |
1596 | } else if (is_readable($CFG->dirroot .'/lang/'. $CFG->lang .'/help/forum/ratings.html')) { | |
d4419d55 | 1597 | $file = file($CFG->dirroot .'/lang/'. $CFG->lang .'/help/forum/ratings.html'); |
ee6e91d4 | 1598 | } else if ($parentlang and is_readable($CFG->dataroot .'/lang/'. $parentlang .'/help/forum/ratings.html')) { |
1599 | $file = file($CFG->dataroot .'/lang/'. $parentlang .'/help/forum/ratings.html'); | |
d4419d55 | 1600 | } else if ($parentlang and is_readable($CFG->dirroot .'/lang/'. $parentlang .'/help/forum/ratings.html')) { |
1601 | $file = file($CFG->dirroot .'/lang/'. $parentlang .'/help/forum/ratings.html'); | |
3a915b06 PS |
1602 | } else if (is_readable($CFG->dirroot .'/lang/en/help/forum/ratings.html')) { |
1603 | $file = file($CFG->dirroot .'/lang/en/help/forum/ratings.html'); | |
02ebf404 | 1604 | } else { |
d4419d55 | 1605 | $file = ''; |
02ebf404 | 1606 | } |
1607 | ||
c3df0901 | 1608 | $defaultscale->description = implode('', $file); |
02ebf404 | 1609 | |
c3df0901 | 1610 | if ($defaultscale->id = $DB->insert_record('scale', $defaultscale)) { |
1611 | $DB->execute("UPDATE {forum} SET scale = ?", array($defaultscale->id)); | |
02ebf404 | 1612 | } |
1613 | } | |
1614 | ||
fbc21ae8 | 1615 | |
18a97fd8 | 1616 | /** |
fbc21ae8 | 1617 | * Returns a menu of all available scales from the site as well as the given course |
1618 | * | |
3564771d | 1619 | * @global object |
fbc21ae8 | 1620 | * @param int $courseid The id of the course as found in the 'course' table. |
3564771d | 1621 | * @return array |
fbc21ae8 | 1622 | */ |
02ebf404 | 1623 | function get_scales_menu($courseid=0) { |
c3df0901 | 1624 | global $DB; |
02ebf404 | 1625 | |
c3df0901 | 1626 | $sql = "SELECT id, name |
1627 | FROM {scale} | |
1628 | WHERE courseid = 0 or courseid = ? | |
02ebf404 | 1629 | ORDER BY courseid ASC, name ASC"; |
c3df0901 | 1630 | $params = array($courseid); |
02ebf404 | 1631 | |
c3df0901 | 1632 | if ($scales = $DB->get_records_sql_menu($sql, $params)) { |
02ebf404 | 1633 | return $scales; |
1634 | } | |
1635 | ||
1636 | make_default_scale(); | |
1637 | ||
c3df0901 | 1638 | return $DB->get_records_sql_menu($sql, $params); |
02ebf404 | 1639 | } |
1640 | ||
5baa0ad6 | 1641 | |
1642 | ||
1643 | /** | |
1644 | * Given a set of timezone records, put them in the database, replacing what is there | |
1645 | * | |
3564771d | 1646 | * @global object |
5baa0ad6 | 1647 | * @param array $timezones An array of timezone records |
3564771d | 1648 | * @return void |
5baa0ad6 | 1649 | */ |
1650 | function update_timezone_records($timezones) { | |
c3df0901 | 1651 | global $DB; |
5baa0ad6 | 1652 | |
1653 | /// Clear out all the old stuff | |
b820eb8c | 1654 | $DB->delete_records('timezone'); |
5baa0ad6 | 1655 | |
1656 | /// Insert all the new stuff | |
1657 | foreach ($timezones as $timezone) { | |
a599aeeb | 1658 | if (is_array($timezone)) { |
1659 | $timezone = (object)$timezone; | |
1660 | } | |
c3df0901 | 1661 | $DB->insert_record('timezone', $timezone); |
5baa0ad6 | 1662 | } |
1663 | } | |
1664 | ||
1665 | ||
df28d6c5 | 1666 | /// MODULE FUNCTIONS ///////////////////////////////////////////////// |
1667 | ||
18a97fd8 | 1668 | /** |
fbc21ae8 | 1669 | * Just gets a raw list of all modules in a course |
1670 | * | |
3564771d | 1671 | * @global object |
fbc21ae8 | 1672 | * @param int $courseid The id of the course as found in the 'course' table. |
3564771d | 1673 | * @return array |
fbc21ae8 | 1674 | */ |
9fa49e22 | 1675 | function get_course_mods($courseid) { |
c3df0901 | 1676 | global $DB; |
9fa49e22 | 1677 | |
3a11c548 | 1678 | if (empty($courseid)) { |
1679 | return false; // avoid warnings | |
1680 | } | |
1681 | ||
c3df0901 | 1682 | return $DB->get_records_sql("SELECT cm.*, m.name as modname |
1683 | FROM {modules} m, {course_modules} cm | |
1684 | WHERE cm.course = ? AND cm.module = m.id AND m.visible = 1", | |
1685 | array($courseid)); // no disabled mods | |
9fa49e22 | 1686 | } |
1687 | ||
fbc21ae8 | 1688 | |
18a97fd8 | 1689 | /** |
f9d5371b | 1690 | * Given an id of a course module, finds the coursemodule description |
fbc21ae8 | 1691 | * |
3564771d | 1692 | * @global object |
a1b892cc | 1693 | * @param string $modulename name of module type, eg. resource, assignment,... (optional, slower and less safe if not specified) |
f9d5371b | 1694 | * @param int $cmid course module id (id in course_modules table) |
1695 | * @param int $courseid optional course id for extra validation | |
a1b892cc | 1696 | * @param bool $sectionnum include relative section number (0,1,2 ...) |
4fea29e4 | 1697 | * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; |
1698 | * IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended); | |
1699 | * MUST_EXIST means throw exception if no record or multiple records found | |
3564771d | 1700 | * @return array Array of results |
f9d5371b | 1701 | */ |
4fea29e4 | 1702 | function get_coursemodule_from_id($modulename, $cmid, $courseid=0, $sectionnum=false, $strictness=IGNORE_MISSING) { |
c3df0901 | 1703 | global $DB; |
f9d5371b | 1704 | |
a1b892cc | 1705 | $params = array('cmid'=>$cmid); |
1706 | ||
1707 | if (!$modulename) { | |
1708 | if (!$modulename = $DB->get_field_sql("SELECT md.name | |
1709 | FROM {modules} md | |
1710 | JOIN {course_modules} cm ON cm.module = md.id | |
4fea29e4 | 1711 | WHERE cm.id = :cmid", $params, $strictness)) { |
a1b892cc | 1712 | return false; |
1713 | } | |
1714 | } | |
1715 | ||
1716 | $params['modulename'] = $modulename; | |
1717 | ||
d251907c | 1718 | $courseselect = ""; |
a1b892cc | 1719 | $sectionfield = ""; |
1720 | $sectionjoin = ""; | |
f9d5371b | 1721 | |
c3df0901 | 1722 | if ($courseid) { |
a1b892cc | 1723 | $courseselect = "AND cm.course = :courseid"; |
c3df0901 | 1724 | $params['courseid'] = $courseid; |
d251907c | 1725 | } |
c3df0901 | 1726 | |
a1b892cc | 1727 | if ($sectionnum) { |
1728 | $sectionfield = ", cw.section AS sectionnum"; | |
1729 | $sectionjoin = "LEFT JOIN {course_sections} cw ON cw.id = cm.section"; | |
1730 | } | |
1731 | ||
1732 | $sql = "SELECT cm.*, m.name, md.name AS modname $sectionfield | |
1733 | FROM {course_modules} cm | |
1734 | JOIN {modules} md ON md.id = cm.module | |
1735 | JOIN {".$modulename."} m ON m.id = cm.instance | |
1736 | $sectionjoin | |
1737 | WHERE cm.id = :cmid AND md.name = :modulename | |
1738 | $courseselect"; | |
1739 | ||
4fea29e4 | 1740 | return $DB->get_record_sql($sql, $params, $strictness); |
f9d5371b | 1741 | } |
1742 | ||
1743 | /** | |
1744 | * Given an instance number of a module, finds the coursemodule description | |
1745 | * | |
3564771d | 1746 | * @global object |
f9d5371b | 1747 | * @param string $modulename name of module type, eg. resource, assignment,... |
1748 | * @param int $instance module instance number (id in resource, assignment etc. table) | |
1749 | * @param int $courseid optional course id for extra validation | |
a1b892cc | 1750 | * @param bool $sectionnum include relative section number (0,1,2 ...) |
4fea29e4 | 1751 | * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; |
1752 | * IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended); | |
1753 | * MUST_EXIST means throw exception if no record or multiple records found | |
3564771d | 1754 | * @return array Array of results |
fbc21ae8 | 1755 | */ |
4fea29e4 | 1756 | function get_coursemodule_from_instance($modulename, $instance, $courseid=0, $sectionnum=false, $strictness=IGNORE_MISSING) { |
c3df0901 | 1757 | global $DB; |
df28d6c5 | 1758 | |
a1b892cc | 1759 | $params = array('instance'=>$instance, 'modulename'=>$modulename); |
1760 | ||
d251907c | 1761 | $courseselect = ""; |
a1b892cc | 1762 | $sectionfield = ""; |
1763 | $sectionjoin = ""; | |
df28d6c5 | 1764 | |
c3df0901 | 1765 | if ($courseid) { |
a1b892cc | 1766 | $courseselect = "AND cm.course = :courseid"; |
c3df0901 | 1767 | $params['courseid'] = $courseid; |
d251907c | 1768 | } |
c3df0901 | 1769 | |
a1b892cc | 1770 | if ($sectionnum) { |
1771 | $sectionfield = ", cw.section AS sectionnum"; | |
1772 | $sectionjoin = "LEFT JOIN {course_sections} cw ON cw.id = cm.section"; | |
1773 | } | |
1774 | ||
1775 | $sql = "SELECT cm.*, m.name, md.name AS modname $sectionfield | |
1776 | FROM {course_modules} cm | |
1777 | JOIN {modules} md ON md.id = cm.module | |
1778 | JOIN {".$modulename."} m ON m.id = cm.instance | |
1779 | $sectionjoin | |
1780 | WHERE m.id = :instance AND md.name = :modulename | |
1781 | $courseselect"; | |
df28d6c5 | 1782 | |
4fea29e4 | 1783 | return $DB->get_record_sql($sql, $params, $strictness); |
df28d6c5 | 1784 | } |
1785 | ||
dd97c328 | 1786 | /** |
1787 | * Returns all course modules of given activity in course | |
3564771d | 1788 | * |
1789 | * @param string $modulename The module name (forum, quiz, etc.) | |
1790 | * @param int $courseid The course id to get modules for | |
dd97c328 | 1791 | * @param string $extrafields extra fields starting with m. |
3564771d | 1792 | * @return array Array of results |
dd97c328 | 1793 | */ |
1794 | function get_coursemodules_in_course($modulename, $courseid, $extrafields='') { | |
c3df0901 | 1795 | global $DB; |
dd97c328 | 1796 | |
1797 | if (!empty($extrafields)) { | |
1798 | $extrafields = ", $extrafields"; | |
1799 | } | |
c3df0901 | 1800 | $params = array(); |
1801 | $params['courseid'] = $courseid; | |
1802 | $params['modulename'] = $modulename; | |
1803 | ||
1804 | ||
1805 | return $DB->get_records_sql("SELECT cm.*, m.name, md.name as modname $extrafields | |
1806 | FROM {course_modules} cm, {modules} md, {".$modulename."} m | |
1807 | WHERE cm.course = :courseid AND | |
1808 | cm.instance = m.id AND | |
1809 | md.name = :modulename AND | |
e0985504 | 1810 | md.id = cm.module", $params); |
dd97c328 | 1811 | } |
ac0b1a19 | 1812 | |
185cfb09 | 1813 | /** |
1814 | * Returns an array of all the active instances of a particular module in given courses, sorted in the order they are defined | |
1815 | * | |
1816 | * Returns an array of all the active instances of a particular | |
1817 | * module in given courses, sorted in the order they are defined | |
ac0b1a19 | 1818 | * in the course. Returns an empty array on any errors. |
185cfb09 | 1819 | * |
ac0b1a19 | 1820 | * The returned objects includle the columns cw.section, cm.visible, |
1821 | * cm.groupmode and cm.groupingid, cm.groupmembersonly, and are indexed by cm.id. | |
1822 | * | |
3564771d | 1823 | * @global object |
1824 | * @global object | |
ac0b1a19 | 1825 | * @param string $modulename The name of the module to get instances for |
1826 | * @param array $courses an array of course objects. | |
3564771d | 1827 | * @param int $userid |
1828 | * @param int $includeinvisible | |
ac0b1a19 | 1829 | * @return array of module instance objects, including some extra fields from the course_modules |
1830 | * and course_sections tables, or an empty array if an error occurred. | |
185cfb09 | 1831 | */ |
00e12c73 | 1832 | function get_all_instances_in_courses($modulename, $courses, $userid=NULL, $includeinvisible=false) { |
c3df0901 | 1833 | global $CFG, $DB; |
ac0b1a19 | 1834 | |
1835 | $outputarray = array(); | |
1836 | ||
185cfb09 | 1837 | if (empty($courses) || !is_array($courses) || count($courses) == 0) { |
ac0b1a19 | 1838 | return $outputarray; |
185cfb09 | 1839 | } |
ac0b1a19 | 1840 | |
c3df0901 | 1841 | list($coursessql, $params) = $DB->get_in_or_equal(array_keys($courses), SQL_PARAMS_NAMED, 'c0'); |
1842 | $params['modulename'] = $modulename; | |
1843 | ||
1844 | if (!$rawmods = $DB->get_records_sql("SELECT cm.id AS coursemodule, m.*, cw.section, cm.visible AS visible, | |
1845 | cm.groupmode, cm.groupingid, cm.groupmembersonly | |
1846 | FROM {course_modules} cm, {course_sections} cw, {modules} md, | |
1847 | {".$modulename."} m | |
1848 | WHERE cm.course $coursessql AND | |
1849 | cm.instance = m.id AND | |
1850 | cm.section = cw.id AND | |
1851 | md.name = :modulename AND | |
1852 | md.id = cm.module", $params)) { | |
ac0b1a19 | 1853 | return $outputarray; |
185cfb09 | 1854 | } |
1855 | ||
185cfb09 | 1856 | foreach ($courses as $course) { |
ac0b1a19 | 1857 | $modinfo = get_fast_modinfo($course, $userid); |
fea43a7f | 1858 | |
ac0b1a19 | 1859 | if (empty($modinfo->instances[$modulename])) { |
185cfb09 | 1860 | continue; |
1861 | } | |
ac0b1a19 | 1862 | |
1863 | foreach ($modinfo->instances[$modulename] as $cm) { | |
1864 | if (!$includeinvisible and !$cm->uservisible) { | |
1865 | continue; | |
1866 | } | |
1867 | if (!isset($rawmods[$cm->id])) { | |
1868 | continue; | |
185cfb09 | 1869 | } |
ac0b1a19 | 1870 | $instance = $rawmods[$cm->id]; |
1871 | if (!empty($cm->extra)) { | |
9a9012dc | 1872 | $instance->extra = $cm->extra; |
ac0b1a19 | 1873 | } |
1874 | $outputarray[] = $instance; | |
185cfb09 | 1875 | } |
1876 | } | |
1877 | ||
1878 | return $outputarray; | |
185cfb09 | 1879 | } |
fbc21ae8 | 1880 | |
18a97fd8 | 1881 | /** |
3d96cba7 | 1882 | * Returns an array of all the active instances of a particular module in a given course, |
1883 | * sorted in the order they are defined. | |
fbc21ae8 | 1884 | * |
1885 | * Returns an array of all the active instances of a particular | |
1886 | * module in a given course, sorted in the order they are defined | |
3d96cba7 | 1887 | * in the course. Returns an empty array on any errors. |
1888 | * | |
1889 | * The returned objects includle the columns cw.section, cm.visible, | |
ac0b1a19 | 1890 | * cm.groupmode and cm.groupingid, cm.groupmembersonly, and are indexed by cm.id. |
fbc21ae8 | 1891 | * |
3564771d | 1892 | * Simply calls {@link all_instances_in_courses()} with a single provided course |
1893 | * | |
3d96cba7 | 1894 | * @param string $modulename The name of the module to get instances for |
ac0b1a19 | 1895 | * @param object $course The course obect. |
3d96cba7 | 1896 | * @return array of module instance objects, including some extra fields from the course_modules |
1897 | * and course_sections tables, or an empty array if an error occurred. | |
3564771d | 1898 | * @param int $userid |
1899 | * @param int $includeinvisible | |
fbc21ae8 | 1900 | */ |
00e12c73 | 1901 | function get_all_instances_in_course($modulename, $course, $userid=NULL, $includeinvisible=false) { |
ac0b1a19 | 1902 | return get_all_instances_in_courses($modulename, array($course->id => $course), $userid, $includeinvisible); |
df28d6c5 | 1903 | } |
1904 | ||
9fa49e22 | 1905 | |
18a97fd8 | 1906 | /** |
fbc21ae8 | 1907 | * Determine whether a module instance is visible within a course |
1908 | * | |
1909 | * Given a valid module object with info about the id and course, | |
1910 | * and the module's type (eg "forum") returns whether the object | |
dd97c328 | 1911 | * is visible or not, groupmembersonly visibility not tested |
fbc21ae8 | 1912 | * |
3564771d | 1913 | * @global object |
117bd748 | 1914 | |
613bbd7c | 1915 | * @param $moduletype Name of the module eg 'forum' |
1916 | * @param $module Object which is the instance of the module | |
3564771d | 1917 | * @return bool Success |
fbc21ae8 | 1918 | */ |
580f2fbc | 1919 | function instance_is_visible($moduletype, $module) { |
c3df0901 | 1920 | global $DB; |
580f2fbc | 1921 | |
2b49ae96 | 1922 | if (!empty($module->id)) { |
d251907c | 1923 | $params = array('courseid'=>$module->course, 'moduletype'=>$moduletype, 'moduleid'=>$module->id); |
c3df0901 | 1924 | if ($records = $DB->get_records_sql("SELECT cm.instance, cm.visible, cm.groupingid, cm.id, cm.groupmembersonly, cm.course |
1925 | FROM {course_modules} cm, {modules} m | |
1926 | WHERE cm.course = :courseid AND | |
1927 | cm.module = m.id AND | |
1928 | m.name = :moduletype AND | |
f93ea222 | 1929 | cm.instance = :moduleid", $params)) { |
5930cded | 1930 | |
2b49ae96 | 1931 | foreach ($records as $record) { // there should only be one - use the first one |
dd97c328 | 1932 | return $record->visible; |
2b49ae96 | 1933 | } |
580f2fbc | 1934 | } |
1935 | } | |
580f2fbc | 1936 | return true; // visible by default! |
1937 | } | |
1938 | ||
dd97c328 | 1939 | /** |
1940 | * Determine whether a course module is visible within a course, | |
1941 | * this is different from instance_is_visible() - faster and visibility for user | |
1942 | * | |
3564771d | 1943 | * @global object |
1944 | * @global object | |
1945 | * @uses DEBUG_DEVELOPER | |
1946 | * @uses CONTEXT_MODULE | |
1947 | * @uses CONDITION_MISSING_EXTRATABLE | |
dd97c328 | 1948 | * @param object $cm object |
1949 | * @param int $userid empty means current user | |
3564771d | 1950 | * @return bool Success |
dd97c328 | 1951 | */ |
1952 | function coursemodule_visible_for_user($cm, $userid=0) { | |
82bd6a5e | 1953 | global $USER,$CFG; |
dd97c328 | 1954 | |
1955 | if (empty($cm->id)) { | |
1956 | debugging("Incorrect course module parameter!", DEBUG_DEVELOPER); | |
1957 | return false; | |
1958 | } | |
1959 | if (empty($userid)) { | |
1960 | $userid = $USER->id; | |
1961 | } | |
1962 | if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_MODULE, $cm->id), $userid)) { | |
1963 | return false; | |
1964 | } | |
82bd6a5e | 1965 | if ($CFG->enableavailability) { |
1966 | require_once($CFG->libdir.'/conditionlib.php'); | |
1967 | $ci=new condition_info($cm,CONDITION_MISSING_EXTRATABLE); | |
117bd748 PS |
1968 | if(!$ci->is_available($cm->availableinfo,false,$userid) and |
1969 | !has_capability('moodle/course:viewhiddenactivities', | |
82bd6a5e | 1970 | get_context_instance(CONTEXT_MODULE, $cm->id), $userid)) { |
1971 | return false; | |
1972 | } | |
1973 | } | |
dd97c328 | 1974 | return groups_course_module_visible($cm, $userid); |
1975 | } | |
1976 | ||
a3fb1c45 | 1977 | |
1978 | ||
1979 | ||
9fa49e22 | 1980 | /// LOG FUNCTIONS ///////////////////////////////////////////////////// |
1981 | ||
1982 | ||
18a97fd8 | 1983 | /** |
fbc21ae8 | 1984 | * Add an entry to the log table. |
1985 | * | |
1986 | * Add an entry to the log table. These are "action" focussed rather | |
1987 | * than web server hits, and provide a way to easily reconstruct what | |
1988 | * any particular student has been doing. | |
1989 | * | |
3564771d | 1990 | * @global object |
1991 | * @global object | |
1992 | * @global object | |
fbc21ae8 | 1993 | * @uses SITEID |
3564771d | 1994 | * @uses DEBUG_DEVELOPER |
1995 | * @uses DEBUG_ALL | |
89dcb99d | 1996 | * @param int $courseid The course id |
fbc21ae8 | 1997 | * @param string $module The module name - e.g. forum, journal, resource, course, user etc |
f7664880 | 1998 | * @param string $action 'view', 'update', 'add' or 'delete', possibly followed by another word to clarify. |
fbc21ae8 | 1999 | * @param string $url The file and parameters used to see the results of the action |
2000 | * @param string $info Additional description information | |
2001 | * @param string $cm The course_module->id if there is one | |
2002 | * @param string $user If log regards $user other than $USER | |
3564771d | 2003 | * @return void |
fbc21ae8 | 2004 | */ |
d4419d55 | 2005 | function add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user=0) { |
e8395a09 | 2006 | // Note that this function intentionally does not follow the normal Moodle DB access idioms. |
2007 | // This is for a good reason: it is the most frequently used DB update function, | |
2008 | // so it has been optimised for speed. | |
f33e1ed4 | 2009 | global $DB, $CFG, $USER; |
9fa49e22 | 2010 | |
7a5b1fc5 | 2011 | if ($cm === '' || is_null($cm)) { // postgres won't translate empty string to its default |
f78b3c34 | 2012 | $cm = 0; |
2013 | } | |
2014 | ||
3d94772d | 2015 | if ($user) { |
2016 | $userid = $user; | |
2017 | } else { | |
b7b64ff2 | 2018 | if (session_is_loggedinas()) { // Don't log |
3d94772d | 2019 | return; |
2020 | } | |
d4419d55 | 2021 | $userid = empty($USER->id) ? '0' : $USER->id; |
9fa49e22 | 2022 | } |
2023 | ||
fcaff7ff | 2024 | $REMOTE_ADDR = getremoteaddr(); |
94b85686 | 2025 | if (empty($REMOTE_ADDR)) { |
2026 | $REMOTE_ADDR = '0.0.0.0'; | |
2027 | } | |
fcaff7ff | 2028 | |
9fa49e22 | 2029 | $timenow = time(); |
ac1ba33e | 2030 | $info = $info; |
10a760b9 | 2031 | if (!empty($url)) { // could break doing html_entity_decode on an empty var. |
2032 | $url = html_entity_decode($url); // for php < 4.3.0 this is defined in moodlelib.php | |
2033 | } | |
853df85e | 2034 | |
6c5a2108 | 2035 | // Restrict length of log lines to the space actually available in the |
2036 | // database so that it doesn't cause a DB error. Log a warning so that | |
2037 | // developers can avoid doing things which are likely to cause this on a | |
2038 | // routine basis. | |
ac1ba33e | 2039 | $tl = textlib_get_instance(); |
6c5a2108 | 2040 | if(!empty($info) && $tl->strlen($info)>255) { |
ac1ba33e | 2041 | $info = $tl->substr($info,0,252).'...'; |
6c5a2108 | 2042 | debugging('Warning: logged very long info',DEBUG_DEVELOPER); |
2043 | } | |
ac1ba33e | 2044 | |
6c5a2108 | 2045 | // If the 100 field size is changed, also need to alter print_log in course/lib.php |
2046 | if(!empty($url) && $tl->strlen($url)>100) { | |
2047 | $url=$tl->substr($url,0,97).'...'; | |
2048 | debugging('Warning: logged very long URL',DEBUG_DEVELOPER); | |
2049 | } | |
8a445484 | 2050 | |
f33e1ed4 | 2051 | if (defined('MDL_PERFDB')) { global $PERF ; $PERF->logwrites++;}; |
853df85e | 2052 | |
f33e1ed4 | 2053 | $log = array('time'=>$timenow, 'userid'=>$userid, 'course'=>$courseid, 'ip'=>$REMOTE_ADDR, 'module'=>$module, |
2054 | 'cmid'=>$cm, 'action'=>$action, 'url'=>$url, 'info'=>$info); | |
2055 | $result = $DB->insert_record_raw('log', $log, false); | |
ebc3bd2b | 2056 | |
9f064546 | 2057 | // MDL-11893, alert $CFG->supportemail if insert into log failed |
f33e1ed4 | 2058 | if (!$result and $CFG->supportemail and empty($CFG->noemailever)) { |
2059 | // email_to_user is not usable because email_to_user tries to write to the logs table, | |
2060 | // and this will get caught in an infinite loop, if disk is full | |
9f064546 | 2061 | $site = get_site(); |
2062 | $subject = 'Insert into log failed at your moodle site '.$site->fullname; | |
0fdf06cd | 2063 | $message = "Insert into log table failed at ". date('l dS \of F Y h:i:s A') .".\n It is possible that your disk is full.\n\n"; |
f33e1ed4 | 2064 | $message .= "The failed query parameters are:\n\n" . var_export($log, true); |
0fdf06cd | 2065 | |
f33e1ed4 | 2066 | $lasttime = get_config('admin', 'lastloginserterrormail'); |
2067 | if(empty($lasttime) || time() - $lasttime > 60*60*24) { // limit to 1 email per day | |
2068 | mail($CFG->supportemail, $subject, $message); | |
2069 | set_config('lastloginserterrormail', time(), 'admin'); | |
58538527 | 2070 | } |
9f064546 | 2071 | } |
2072 | ||
252720c4 | 2073 | if (!$result) { |
2074 | debugging('Error: Could not insert a new entry to the Moodle log', DEBUG_ALL); | |
8f0cd6ef | 2075 | } |
cb80265b | 2076 | |
341b5ed2 | 2077 | } |
2078 | ||
2079 | /** | |
2080 | * Store user last access times - called when use enters a course or site | |
2081 | * | |
3564771d | 2082 | * @global object |
2083 | * @global object | |
2084 | * @global object | |
2085 | * @uses LASTACCESS_UPDATE_SECS | |
2086 | * @uses SITEID | |
341b5ed2 | 2087 | * @param int $courseid, empty means site |
2088 | * @return void | |
2089 | */ | |
2090 | function user_accesstime_log($courseid=0) { | |
f33e1ed4 | 2091 | global $USER, $CFG, $DB; |
341b5ed2 | 2092 | |
b7b64ff2 | 2093 | if (!isloggedin() or session_is_loggedinas()) { |
341b5ed2 | 2094 | // no access tracking |
2095 | return; | |
2096 | } | |
2097 | ||
2098 | if (empty($courseid)) { | |
2099 | $courseid = SITEID; | |
2100 | } | |
2101 | ||
2102 | $timenow = time(); | |
2103 | ||
2104 | /// Store site lastaccess time for the current user | |
2105 | if ($timenow - $USER->lastaccess > LASTACCESS_UPDATE_SECS) { | |
2106 | /// Update $USER->lastaccess for next checks | |
2107 | $USER->lastaccess = $timenow; | |
341b5ed2 | 2108 | |
f33e1ed4 | 2109 | $last = new object(); |
2110 | $last->id = $USER->id; | |
2111 | $last->lastip = getremoteaddr(); | |
2112 | $last->lastaccess = $timenow; | |
2113 | ||
68fbad44 | 2114 | $DB->update_record_raw('user', $last); |
341b5ed2 | 2115 | } |
2116 | ||
2117 | if ($courseid == SITEID) { | |
2118 | /// no user_lastaccess for frontpage | |
2119 | return; | |
2120 | } | |
cb8aaedf | 2121 | |
341b5ed2 | 2122 | /// Store course lastaccess times for the current user |
2123 | if (empty($USER->currentcourseaccess[$courseid]) or ($timenow - $USER->currentcourseaccess[$courseid] > LASTACCESS_UPDATE_SECS)) { | |
341b5ed2 | 2124 | |
f33e1ed4 | 2125 | $lastaccess = $DB->get_field('user_lastaccess', 'timeaccess', array('userid'=>$USER->id, 'courseid'=>$courseid)); |
341b5ed2 | 2126 | |
f33e1ed4 | 2127 | if ($lastaccess === false) { |
2128 | // Update course lastaccess for next checks | |
2129 | $USER->currentcourseaccess[$courseid] = $timenow; | |
2130 | ||
2131 | $last = new object(); | |
2132 | $last->userid = $USER->id; | |
2133 | $last->courseid = $courseid; | |
2134 | $last->timeaccess = $timenow; | |
68fbad44 | 2135 | $DB->insert_record_raw('user_lastaccess', $last, false); |
d251907c | 2136 | |
f33e1ed4 | 2137 | } else if ($timenow - $lastaccess < LASTACCESS_UPDATE_SECS) { |
2138 | // no need to update now, it was updated recently in concurrent login ;-) | |
341b5ed2 | 2139 | |
f33e1ed4 | 2140 | } else { |
2141 | // Update course lastaccess for next checks | |
2142 | $USER->currentcourseaccess[$courseid] = $timenow; | |
2143 | ||
68fbad44 | 2144 | $DB->set_field('user_lastaccess', 'timeaccess', $timenow, array('userid'=>$USER->id, 'courseid'=>$courseid)); |
3d94772d | 2145 | } |
8f0cd6ef | 2146 | } |
9fa49e22 | 2147 | } |
2148 | ||
18a97fd8 | 2149 | /** |
fbc21ae8 | 2150 | * Select all log records based on SQL criteria |
2151 | * | |
3564771d | 2152 | * @todo Finish documenting this function |
2153 | * | |
2154 | * @global object | |
fbc21ae8 | 2155 | * @param string $select SQL select criteria |
c3df0901 | 2156 | * @param array $params named sql type params |
fbc21ae8 | 2157 | * @param string $order SQL order by clause to sort the records returned |
2158 | * @param string $limitfrom ? | |
2159 | * @param int $limitnum ? | |
2160 | * @param int $totalcount Passed in by reference. | |
7290c7fa | 2161 | * @return object |
fbc21ae8 | 2162 | */ |
c3df0901 | 2163 | function get_logs($select, array $params=null, $order='l.time DESC', $limitfrom='', $limitnum='', &$totalcount) { |
2164 | global $DB; | |
9fa49e22 | 2165 | |
519d369f | 2166 | if ($order) { |
c3df0901 | 2167 | $order = "ORDER BY $order"; |
2168 | } | |
2169 | ||
2170 | $selectsql = ""; | |
2171 | $countsql = ""; | |
2172 | ||
2173 | if ($select) { | |
2174 | $select = "WHERE $select"; | |
519d369f | 2175 | } |
2176 | ||
c3df0901 | 2177 | $sql = "SELECT COUNT(*) |
2178 | FROM {log} l | |
2179 | $select"; | |
2180 | ||
2181 | $totalcount = $DB->count_records_sql($sql, $params); | |
a2ddd957 | 2182 | |
c3df0901 | 2183 | $sql = "SELECT l.*, u.firstname, u.lastname, u.picture |
d251907c | 2184 | FROM {log} l |
c3df0901 | 2185 | LEFT JOIN {user} u ON l.userid = u.id |
d251907c | 2186 | $select |
c3df0901 | 2187 | $order"; |
519d369f | 2188 | |
c3df0901 | 2189 | return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum) ; |
9fa49e22 | 2190 | } |
2191 | ||
519d369f | 2192 | |
18a97fd8 | 2193 | /** |
fbc21ae8 | 2194 | * Select all log records for a given course and user |
2195 | * | |
3564771d | 2196 | * @todo Finish documenting this function |
2197 | * | |
2198 | * @global object | |
2f87145b | 2199 | * @uses DAYSECS |
fbc21ae8 | 2200 | * @param int $userid The id of the user as found in the 'user' table. |
2201 | * @param int $courseid The id of the course as found in the 'course' table. | |
2202 | * @param string $coursestart ? | |
fbc21ae8 | 2203 | */ |
9fa49e22 | 2204 | function get_logs_usercourse($userid, $courseid, $coursestart) { |
c3df0901 | 2205 | global $DB; |
9fa49e22 | 2206 | |
c3df0901 | 2207 | $params = array(); |
2208 | ||
2209 | $courseselect = ''; | |
da0c90c3 | 2210 | if ($courseid) { |
c3df0901 | 2211 | $courseselect = "AND course = :courseid"; |
d251907c | 2212 | $params['courseid'] = $courseid; |
da0c90c3 | 2213 | } |
c3df0901 | 2214 | $params['userid'] = $userid; |
d251907c | 2215 | $params['coursestart'] = $coursestart; |
da0c90c3 | 2216 | |
c3df0901 | 2217 | return $DB->get_records_sql("SELECT FLOOR((time - :coursestart)/". DAYSECS .") AS day, COUNT(*) AS num |
2218 | FROM {log} | |
2219 | WHERE userid = :userid | |
2220 | AND time > :coursestart $courseselect | |
2221 | GROUP BY FLOOR((time - :coursestart)/". DAYSECS .")", $params); | |
9fa49e22 | 2222 | } |
2223 | ||
18a97fd8 | 2224 | /** |
fbc21ae8 | 2225 | * Select all log records for a given course, user, and day |
2226 | * | |
3564771d | 2227 | * @global object |
2f87145b | 2228 | * @uses HOURSECS |
fbc21ae8 | 2229 | * @param int $userid The id of the user as found in the 'user' table. |
2230 | * @param int $courseid The id of the course as found in the 'course' table. | |
2231 | * @param string $daystart ? | |
7290c7fa | 2232 | * @return object |
fbc21ae8 | 2233 | */ |
9fa49e22 | 2234 | function get_logs_userday($userid, $courseid, $daystart) { |
c3df0901 | 2235 | global $DB; |
2236 | ||
66a0612a | 2237 | $params = array($daystart, $userid, $daystart); |
9fa49e22 | 2238 | |
c3df0901 | 2239 | $courseselect = ''; |
7e4a6488 | 2240 | if ($courseid) { |
66a0612a SH |
2241 | $courseselect = "AND course = ?"; |
2242 | $params[] = $courseid; | |
7e4a6488 | 2243 | } |
66a0612a | 2244 | $params[] = $daystart; |
7e4a6488 | 2245 | |
66a0612a | 2246 | return $DB->get_records_sql("SELECT FLOOR((time - ?)/". HOURSECS .") AS hour, COUNT(*) AS num |
c3df0901 | 2247 | FROM {log} |
66a0612a SH |
2248 | WHERE userid = ? |
2249 | AND time > ? $courseselect | |
2250 | GROUP BY FLOOR((time - ?)/". HOURSECS .") ", $params); | |
9fa49e22 | 2251 | } |
2252 | ||
b4bac9b6 | 2253 | /** |
2254 | * Returns an object with counts of failed login attempts | |
2255 | * | |
8f0cd6ef | 2256 | * Returns information about failed login attempts. If the current user is |
2257 | * an admin, then two numbers are returned: the number of attempts and the | |
b4bac9b6 | 2258 | * number of accounts. For non-admins, only the attempts on the given user |
2259 | * are shown. | |
2260 | * | |
3564771d | 2261 | * @global object |
2262 | * @uses CONTEXT_SYSTEM | |
4f0c2d00 | 2263 | * @param string $mode Either 'admin' or 'everybody' |
fbc21ae8 | 2264 | * @param string $username The username we are searching for |
2265 | * @param string $lastlogin The date from which we are searching | |
2266 | * @return int | |
b4bac9b6 | 2267 | */ |
b4bac9b6 | 2268 | function count_login_failures($mode, $username, $lastlogin) { |
c3df0901 | 2269 | global $DB; |
b4bac9b6 | 2270 | |
c3df0901 | 2271 | $params = array('mode'=>$mode, 'username'=>$username, 'lastlogin'=>$lastlogin); |
2272 | $select = "module='login' AND action='error' AND time > :lastlogin"; | |
2273 | ||
2274 | $count = new object(); | |
b4bac9b6 | 2275 | |
4f0c2d00 | 2276 | if (is_siteadmin()) { |
c3df0901 | 2277 | if ($count->attempts = $DB->count_records_select('log', $select, $params)) { |
2278 | $count->accounts = $DB->count_records_select('log', $select, $params, 'COUNT(DISTINCT info)'); | |
b4bac9b6 | 2279 | return $count; |
2280 | } | |
4f0c2d00 | 2281 | } else if ($mode == 'everybody') { |
c3df0901 | 2282 | if ($count->attempts = $DB->count_records_select('log', "$select AND info = :username", $params)) { |
b4bac9b6 | 2283 | return $count; |
2284 | } | |
2285 | } | |
2286 | return NULL; | |
2287 | } | |
2288 | ||
2289 | ||
a3fb1c45 | 2290 | /// GENERAL HELPFUL THINGS /////////////////////////////////// |
2291 | ||
18a97fd8 | 2292 | /** |
fbc21ae8 | 2293 | * Dump a given object's information in a PRE block. |
2294 | * | |
2295 | * Mostly just used for debugging. | |
2296 | * | |
2297 | * @param mixed $object The data to be printed | |
3564771d | 2298 | * @return void OUtput is echo'd |
fbc21ae8 | 2299 | */ |
a3fb1c45 | 2300 | function print_object($object) { |
1aa7b31d | 2301 | echo '<pre class="notifytiny">' . htmlspecialchars(print_r($object,true)) . '</pre>'; |
a3fb1c45 | 2302 | } |
2303 | ||
624a690b | 2304 | /** |
3511647c | 2305 | * Check whether a course is visible through its parents |
bfbfdb53 | 2306 | * path. |
3511647c | 2307 | * |
2308 | * Notes: | |
2309 | * | |
2310 | * - All we need from the course is ->category. _However_ | |
2311 | * if the course object has a categorypath property, | |
2312 | * we'll save a dbquery | |
2313 | * | |
2314 | * - If we return false, you'll still need to check if | |
8ed5dd63 | 2315 | * the user can has the 'moodle/category:viewhiddencategories' |
3511647c | 2316 | * capability... |
2317 | * | |
bfbfdb53 | 2318 | * - Will generate 2 DB calls. |
3511647c | 2319 | * |
2320 | * - It does have a small local cache, however... | |
2321 | * | |
2322 | * - Do NOT call this over many courses as it'll generate | |
2323 | * DB traffic. Instead, see what get_my_courses() does. | |
2324 | * | |
3564771d | 2325 | * @global object |
2326 | * @global object | |
2327 | * @staticvar array $mycache | |
2328 | * @param object $course A course object | |
3511647c | 2329 | * @return bool |
2330 | */ | |
0986271b | 2331 | function course_parent_visible($course = null) { |
c3df0901 | 2332 | global $CFG, $DB; |
3511647c | 2333 | //return true; |
2334 | static $mycache; | |
fa145ae1 | 2335 | |
3511647c | 2336 | if (!is_object($course)) { |
418b4e5a | 2337 | return true; |
2338 | } | |
2339 | if (!empty($CFG->allowvisiblecoursesinhiddencategories)) { | |
2340 | return true; | |
2341 | } | |
0986271b | 2342 | |
3511647c | 2343 | if (!isset($mycache)) { |
2344 | $mycache = array(); | |
2345 | } else { | |
2346 | // cast to force assoc array | |
bfbfdb53 | 2347 | $k = (string)$course->category; |
3511647c | 2348 | if (isset($mycache[$k])) { |
2349 | return $mycache[$k]; | |
2350 | } | |
0986271b | 2351 | } |
5930cded | 2352 | |
3511647c | 2353 | if (isset($course->categorypath)) { |
2354 | $path = $course->categorypath; | |
2355 | } else { | |
c3df0901 | 2356 | $path = $DB->get_field('course_categories', 'path', array('id'=>$course->category)); |
824f1c40 | 2357 | } |
3511647c | 2358 | $catids = substr($path,1); // strip leading slash |
2359 | $catids = str_replace('/',',',$catids); | |
824f1c40 | 2360 | |
3511647c | 2361 | $sql = "SELECT MIN(visible) |
c3df0901 | 2362 | FROM {course_categories} |
2363 | WHERE id IN ($catids)"; | |
2364 | $vis = $DB->get_field_sql($sql); | |
5930cded | 2365 | |
3511647c | 2366 | // cast to force assoc array |
2367 | $k = (string)$course->category; | |
2368 | $mycache[$k] = $vis; | |
2369 | ||
2370 | return $vis; | |
0986271b | 2371 | } |
2372 | ||
62d4e774 | 2373 | /** |
5930cded | 2374 | * This function is the official hook inside XMLDB stuff to delegate its debug to one |
62d4e774 | 2375 | * external function. |
2376 | * | |
2377 | * Any script can avoid calls to this function by defining XMLDB_SKIP_DEBUG_HOOK before | |
2378 | * using XMLDB classes. Obviously, also, if this function doesn't exist, it isn't invoked ;-) | |
2379 | * | |
3564771d | 2380 | * @uses DEBUG_DEVELOPER |
2381 | * @param string $message string contains the error message | |
2382 | * @param object $object object XMLDB object that fired the debug | |
62d4e774 | 2383 | */ |
2384 | function xmldb_debug($message, $object) { | |
2385 | ||
92b564f4 | 2386 | debugging($message, DEBUG_DEVELOPER); |
62d4e774 | 2387 | } |
2388 | ||
49860445 | 2389 | /** |
3564771d | 2390 | * @global object |
2391 | * @uses CONTEXT_COURSECAT | |
df73f8d4 | 2392 | * @return boolean Whether the user can create courses in any category in the system. |
49860445 | 2393 | */ |
2394 | function user_can_create_courses() { | |
c3df0901 | 2395 | global $DB; |
df73f8d4 | 2396 | $catsrs = $DB->get_recordset('course_categories'); |
4d55255b | 2397 | foreach ($catsrs as $cat) { |
df73f8d4 | 2398 | if (has_capability('moodle/course:create', get_context_instance(CONTEXT_COURSECAT, $cat->id))) { |
2399 | $catsrs->close(); | |
2400 | return true; | |
49860445 | 2401 | } |
2402 | } | |
df73f8d4 | 2403 | $catsrs->close(); |
2404 | return false; | |
49860445 | 2405 | } |