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