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