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