Prevent some warning when restoring site courses... MDL-10054 MDL-10118
[moodle.git] / lib / moodlelib.php
CommitLineData
ef1e97c7 1<?php // $Id$
f9903ed0 2
9fa49e22 3///////////////////////////////////////////////////////////////////////////
4// //
5// NOTICE OF COPYRIGHT //
6// //
7// Moodle - Modular Object-Oriented Dynamic Learning Environment //
abc3b857 8// http://moodle.org //
9fa49e22 9// //
abc3b857 10// Copyright (C) 1999-2004 Martin Dougiamas http://dougiamas.com //
9fa49e22 11// //
12// This program is free software; you can redistribute it and/or modify //
13// it under the terms of the GNU General Public License as published by //
14// the Free Software Foundation; either version 2 of the License, or //
15// (at your option) any later version. //
16// //
17// This program is distributed in the hope that it will be useful, //
18// but WITHOUT ANY WARRANTY; without even the implied warranty of //
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
20// GNU General Public License for more details: //
21// //
22// http://www.gnu.org/copyleft/gpl.html //
23// //
24///////////////////////////////////////////////////////////////////////////
65ccdd8c 25
7cf1c7bd 26/**
89dcb99d 27 * moodlelib.php - Moodle main library
7cf1c7bd 28 *
29 * Main library file of miscellaneous general-purpose Moodle functions.
30 * Other main libraries:
8c3dba73 31 * - weblib.php - functions that produce web output
32 * - datalib.php - functions that access the database
7cf1c7bd 33 * @author Martin Dougiamas
34 * @version $Id$
89dcb99d 35 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
7cf1c7bd 36 * @package moodlecore
37 */
e1ecf0a0 38
bbd3f2c4 39/// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
f374fb10 40
6b94a807 41/**
42 * Used by some scripts to check they are being called by Moodle
43 */
44define('MOODLE_INTERNAL', true);
45
bbd3f2c4 46/// Date and time constants ///
5602f7cf 47/**
48 * Time constant - the number of seconds in a year
49 */
50
51define('YEARSECS', 31536000);
52
7a5672c9 53/**
2f87145b 54 * Time constant - the number of seconds in a week
7a5672c9 55 */
361855e6 56define('WEEKSECS', 604800);
2f87145b 57
58/**
59 * Time constant - the number of seconds in a day
60 */
7a5672c9 61define('DAYSECS', 86400);
2f87145b 62
63/**
64 * Time constant - the number of seconds in an hour
65 */
7a5672c9 66define('HOURSECS', 3600);
2f87145b 67
68/**
69 * Time constant - the number of seconds in a minute
70 */
7a5672c9 71define('MINSECS', 60);
2f87145b 72
73/**
74 * Time constant - the number of minutes in a day
75 */
7a5672c9 76define('DAYMINS', 1440);
2f87145b 77
78/**
79 * Time constant - the number of minutes in an hour
80 */
7a5672c9 81define('HOURMINS', 60);
f9903ed0 82
c59733ef 83/// Parameter constants - every call to optional_param(), required_param() ///
84/// or clean_param() should have a specified type of parameter. //////////////
85
e0d346ff 86/**
038ba6aa 87 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
88 * originally was 0, but changed because we need to detect unknown
89 * parameter types and swiched order in clean_param().
e0d346ff 90 */
038ba6aa 91define('PARAM_RAW', 666);
bbd3f2c4 92
93/**
c59733ef 94 * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
95 * It was one of the first types, that is why it is abused so much ;-)
bbd3f2c4 96 */
2ae28153 97define('PARAM_CLEAN', 0x0001);
bbd3f2c4 98
99/**
c59733ef 100 * PARAM_INT - integers only, use when expecting only numbers.
bbd3f2c4 101 */
2ae28153 102define('PARAM_INT', 0x0002);
bbd3f2c4 103
104/**
105 * PARAM_INTEGER - an alias for PARAM_INT
106 */
107define('PARAM_INTEGER', 0x0002);
108
9dae915a 109/**
5e623a33 110 * PARAM_NUMBER - a real/floating point number.
9dae915a 111 */
112define('PARAM_NUMBER', 0x000a);
113
bbd3f2c4 114/**
c59733ef 115 * PARAM_ALPHA - contains only english letters.
bbd3f2c4 116 */
2ae28153 117define('PARAM_ALPHA', 0x0004);
bbd3f2c4 118
119/**
c59733ef 120 * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
121 * @TODO: should we alias it to PARAM_ALPHANUM ?
bbd3f2c4 122 */
123define('PARAM_ACTION', 0x0004);
124
125/**
c59733ef 126 * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
127 * @TODO: should we alias it to PARAM_ALPHANUM ?
bbd3f2c4 128 */
129define('PARAM_FORMAT', 0x0004);
130
131/**
c59733ef 132 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
bbd3f2c4 133 */
2ae28153 134define('PARAM_NOTAGS', 0x0008);
bbd3f2c4 135
31f26796 136 /**
c4ea5e78 137 * PARAM_MULTILANG - alias of PARAM_TEXT.
31f26796 138 */
139define('PARAM_MULTILANG', 0x0009);
140
c4ea5e78 141 /**
142 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
143 */
144define('PARAM_TEXT', 0x0009);
145
bbd3f2c4 146/**
c59733ef 147 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
bbd3f2c4 148 */
2ae28153 149define('PARAM_FILE', 0x0010);
bbd3f2c4 150
bcef0319 151/**
152 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
153 */
154define('PARAM_TAG', 0x0011);
155
156/**
157 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
158 */
159define('PARAM_TAGLIST', 0x0012);
160
bbd3f2c4 161/**
c59733ef 162 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
163 * note: the leading slash is not removed, window drive letter is not allowed
bbd3f2c4 164 */
2ae28153 165define('PARAM_PATH', 0x0020);
bbd3f2c4 166
167/**
c59733ef 168 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
bbd3f2c4 169 */
170define('PARAM_HOST', 0x0040);
171
172/**
c59733ef 173 * PARAM_URL - expected properly formatted URL.
bbd3f2c4 174 */
2ae28153 175define('PARAM_URL', 0x0080);
bbd3f2c4 176
177/**
c59733ef 178 * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the others! Implies PARAM_URL!)
bbd3f2c4 179 */
180define('PARAM_LOCALURL', 0x0180);
181
182/**
c59733ef 183 * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
184 * use when you want to store a new file submitted by students
bbd3f2c4 185 */
14d6c233 186define('PARAM_CLEANFILE',0x0200);
e0d346ff 187
8bd3fad3 188/**
c59733ef 189 * PARAM_ALPHANUM - expected numbers and letters only.
bbd3f2c4 190 */
191define('PARAM_ALPHANUM', 0x0400);
192
193/**
c59733ef 194 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
bbd3f2c4 195 */
196define('PARAM_BOOL', 0x0800);
197
198/**
c59733ef 199 * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
200 * note: do not forget to addslashes() before storing into database!
bbd3f2c4 201 */
202define('PARAM_CLEANHTML',0x1000);
203
204/**
c59733ef 205 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
206 * suitable for include() and require()
207 * @TODO: should we rename this function to PARAM_SAFEDIRS??
bbd3f2c4 208 */
209define('PARAM_ALPHAEXT', 0x2000);
210
211/**
c59733ef 212 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
bbd3f2c4 213 */
214define('PARAM_SAFEDIR', 0x4000);
215
0e4af166 216/**
217 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
218 */
219define('PARAM_SEQUENCE', 0x8000);
220
03d820c7 221/**
222 * PARAM_PEM - Privacy Enhanced Mail format
223 */
224define('PARAM_PEM', 0x10000);
225
226/**
227 * PARAM_BASE64 - Base 64 encoded format
228 */
229define('PARAM_BASE64', 0x20000);
230
231
bbd3f2c4 232/// Page types ///
233/**
234 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
8bd3fad3 235 */
236define('PAGE_COURSE_VIEW', 'course-view');
8bd3fad3 237
7eb0b60a 238/// Debug levels ///
239/** no warnings at all */
240define ('DEBUG_NONE', 0);
241/** E_ERROR | E_PARSE */
242define ('DEBUG_MINIMAL', 5);
243/** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
244define ('DEBUG_NORMAL', 15);
245/** E_ALL without E_STRICT and E_RECOVERABLE_ERROR for now */
246define ('DEBUG_ALL', 2047);
247/** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
248define ('DEBUG_DEVELOPER', 34815);
bbd3f2c4 249
feaf5d06 250/**
251 * Blog access level constant declaration
252 */
253define ('BLOG_USER_LEVEL', 1);
254define ('BLOG_GROUP_LEVEL', 2);
255define ('BLOG_COURSE_LEVEL', 3);
256define ('BLOG_SITE_LEVEL', 4);
257define ('BLOG_GLOBAL_LEVEL', 5);
258
259
03d820c7 260
9fa49e22 261/// PARAMETER HANDLING ////////////////////////////////////////////////////
6b174680 262
e0d346ff 263/**
361855e6 264 * Returns a particular value for the named variable, taken from
265 * POST or GET. If the parameter doesn't exist then an error is
e0d346ff 266 * thrown because we require this variable.
267 *
361855e6 268 * This function should be used to initialise all required values
269 * in a script that are based on parameters. Usually it will be
e0d346ff 270 * used like this:
271 * $id = required_param('id');
272 *
a083b93c 273 * @param string $parname the name of the page parameter we want
274 * @param int $type expected type of parameter
e0d346ff 275 * @return mixed
276 */
a083b93c 277function required_param($parname, $type=PARAM_CLEAN) {
e0d346ff 278
5d7a9f56 279 // detect_unchecked_vars addition
280 global $CFG;
281 if (!empty($CFG->detect_unchecked_vars)) {
282 global $UNCHECKED_VARS;
a083b93c 283 unset ($UNCHECKED_VARS->vars[$parname]);
5d7a9f56 284 }
285
a083b93c 286 if (isset($_POST[$parname])) { // POST has precedence
287 $param = $_POST[$parname];
288 } else if (isset($_GET[$parname])) {
289 $param = $_GET[$parname];
e0d346ff 290 } else {
a083b93c 291 error('A required parameter ('.$parname.') was missing');
e0d346ff 292 }
293
a083b93c 294 return clean_param($param, $type);
e0d346ff 295}
296
297/**
361855e6 298 * Returns a particular value for the named variable, taken from
e0d346ff 299 * POST or GET, otherwise returning a given default.
300 *
361855e6 301 * This function should be used to initialise all optional values
302 * in a script that are based on parameters. Usually it will be
e0d346ff 303 * used like this:
304 * $name = optional_param('name', 'Fred');
305 *
a083b93c 306 * @param string $parname the name of the page parameter we want
e0d346ff 307 * @param mixed $default the default value to return if nothing is found
a083b93c 308 * @param int $type expected type of parameter
e0d346ff 309 * @return mixed
310 */
a083b93c 311function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
e0d346ff 312
5d7a9f56 313 // detect_unchecked_vars addition
314 global $CFG;
315 if (!empty($CFG->detect_unchecked_vars)) {
316 global $UNCHECKED_VARS;
a083b93c 317 unset ($UNCHECKED_VARS->vars[$parname]);
5d7a9f56 318 }
319
a083b93c 320 if (isset($_POST[$parname])) { // POST has precedence
321 $param = $_POST[$parname];
322 } else if (isset($_GET[$parname])) {
323 $param = $_GET[$parname];
e0d346ff 324 } else {
325 return $default;
326 }
327
a083b93c 328 return clean_param($param, $type);
e0d346ff 329}
330
331/**
361855e6 332 * Used by {@link optional_param()} and {@link required_param()} to
333 * clean the variables and/or cast to specific types, based on
e0d346ff 334 * an options field.
bbd3f2c4 335 * <code>
336 * $course->format = clean_param($course->format, PARAM_ALPHA);
337 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
338 * </code>
e0d346ff 339 *
bbd3f2c4 340 * @uses $CFG
341 * @uses PARAM_CLEAN
342 * @uses PARAM_INT
343 * @uses PARAM_INTEGER
344 * @uses PARAM_ALPHA
345 * @uses PARAM_ALPHANUM
346 * @uses PARAM_NOTAGS
f4f65990 347 * @uses PARAM_ALPHAEXT
bbd3f2c4 348 * @uses PARAM_BOOL
349 * @uses PARAM_SAFEDIR
350 * @uses PARAM_CLEANFILE
351 * @uses PARAM_FILE
352 * @uses PARAM_PATH
353 * @uses PARAM_HOST
354 * @uses PARAM_URL
355 * @uses PARAM_LOCALURL
356 * @uses PARAM_CLEANHTML
0e4af166 357 * @uses PARAM_SEQUENCE
e0d346ff 358 * @param mixed $param the variable we are cleaning
a083b93c 359 * @param int $type expected format of param after cleaning.
e0d346ff 360 * @return mixed
361 */
a083b93c 362function clean_param($param, $type) {
e0d346ff 363
7744ea12 364 global $CFG;
365
80bfd470 366 if (is_array($param)) { // Let's loop
367 $newparam = array();
368 foreach ($param as $key => $value) {
a083b93c 369 $newparam[$key] = clean_param($value, $type);
80bfd470 370 }
371 return $newparam;
372 }
373
a083b93c 374 switch ($type) {
96e98ea6 375 case PARAM_RAW: // no cleaning at all
376 return $param;
377
a083b93c 378 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
379 if (is_numeric($param)) {
380 return $param;
381 }
382 $param = stripslashes($param); // Needed for kses to work fine
383 $param = clean_text($param); // Sweep for scripts, etc
384 return addslashes($param); // Restore original request parameter slashes
3af57507 385
a083b93c 386 case PARAM_CLEANHTML: // prepare html fragment for display, do not store it into db!!
387 $param = stripslashes($param); // Remove any slashes
388 $param = clean_text($param); // Sweep for scripts, etc
389 return trim($param);
e0d346ff 390
a083b93c 391 case PARAM_INT:
392 return (int)$param; // Convert to integer
e0d346ff 393
9dae915a 394 case PARAM_NUMBER:
395 return (float)$param; // Convert to integer
396
a083b93c 397 case PARAM_ALPHA: // Remove everything not a-z
398 return eregi_replace('[^a-zA-Z]', '', $param);
e0d346ff 399
a083b93c 400 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
401 return eregi_replace('[^A-Za-z0-9]', '', $param);
f24148ef 402
a083b93c 403 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z/_-
404 return eregi_replace('[^a-zA-Z/_-]', '', $param);
0ed442f8 405
0e4af166 406 case PARAM_SEQUENCE: // Remove everything not 0-9,
407 return eregi_replace('[^0-9,]', '', $param);
408
a083b93c 409 case PARAM_BOOL: // Convert to 1 or 0
410 $tempstr = strtolower($param);
eb59ac27 411 if ($tempstr == 'on' or $tempstr == 'yes' ) {
a083b93c 412 $param = 1;
eb59ac27 413 } else if ($tempstr == 'off' or $tempstr == 'no') {
a083b93c 414 $param = 0;
415 } else {
416 $param = empty($param) ? 0 : 1;
417 }
418 return $param;
f24148ef 419
a083b93c 420 case PARAM_NOTAGS: // Strip all tags
421 return strip_tags($param);
3af57507 422
c4ea5e78 423 case PARAM_TEXT: // leave only tags needed for multilang
31f26796 424 return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
425
a083b93c 426 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
427 return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
95bfd207 428
a083b93c 429 case PARAM_CLEANFILE: // allow only safe characters
430 return clean_filename($param);
14d6c233 431
a083b93c 432 case PARAM_FILE: // Strip all suspicious characters from filename
433 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
434 $param = ereg_replace('\.\.+', '', $param);
435 if($param == '.') {
371a2ed0 436 $param = '';
437 }
a083b93c 438 return $param;
439
440 case PARAM_PATH: // Strip all suspicious characters from file path
441 $param = str_replace('\\\'', '\'', $param);
442 $param = str_replace('\\"', '"', $param);
443 $param = str_replace('\\', '/', $param);
444 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
445 $param = ereg_replace('\.\.+', '', $param);
446 $param = ereg_replace('//+', '/', $param);
447 return ereg_replace('/(\./)+', '/', $param);
448
449 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
450 preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
451 // match ipv4 dotted quad
452 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
453 // confirm values are ok
454 if ( $match[0] > 255
455 || $match[1] > 255
456 || $match[3] > 255
457 || $match[4] > 255 ) {
458 // hmmm, what kind of dotted quad is this?
459 $param = '';
460 }
461 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
462 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
463 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
464 ) {
465 // all is ok - $param is respected
466 } else {
467 // all is not ok...
468 $param='';
469 }
470 return $param;
7744ea12 471
a083b93c 472 case PARAM_URL: // allow safe ftp, http, mailto urls
473 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
5301205a 474 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
a083b93c 475 // all is ok, param is respected
d2a9f7cc 476 } else {
a083b93c 477 $param =''; // not really ok
478 }
479 return $param;
480
481 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
93684765 482 $param = clean_param($param, PARAM_URL);
a083b93c 483 if (!empty($param)) {
484 if (preg_match(':^/:', $param)) {
485 // root-relative, ok!
486 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
487 // absolute, and matches our wwwroot
7744ea12 488 } else {
a083b93c 489 // relative - let's make sure there are no tricks
490 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
491 // looks ok.
492 } else {
493 $param = '';
494 }
d2a9f7cc 495 }
7744ea12 496 }
a083b93c 497 return $param;
bcef0319 498
03d820c7 499 case PARAM_PEM:
500 $param = trim($param);
501 // PEM formatted strings may contain letters/numbers and the symbols
502 // forward slash: /
503 // plus sign: +
504 // equal sign: =
505 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
506 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
507 list($wholething, $body) = $matches;
508 unset($wholething, $matches);
509 $b64 = clean_param($body, PARAM_BASE64);
510 if (!empty($b64)) {
511 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
512 } else {
513 return '';
514 }
515 }
516 return '';
bcef0319 517
03d820c7 518 case PARAM_BASE64:
519 if (!empty($param)) {
520 // PEM formatted strings may contain letters/numbers and the symbols
521 // forward slash: /
522 // plus sign: +
523 // equal sign: =
03d820c7 524 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
525 return '';
526 }
527 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
528 // Each line of base64 encoded data must be 64 characters in
529 // length, except for the last line which may be less than (or
530 // equal to) 64 characters long.
531 for ($i=0, $j=count($lines); $i < $j; $i++) {
532 if ($i + 1 == $j) {
533 if (64 < strlen($lines[$i])) {
534 return '';
535 }
536 continue;
537 }
7744ea12 538
03d820c7 539 if (64 != strlen($lines[$i])) {
540 return '';
541 }
542 }
543 return implode("\n",$lines);
544 } else {
545 return '';
546 }
bcef0319 547
548 case PARAM_TAG:
549 //first fix whitespace
550 $param = preg_replace('/\s+/', ' ', $param);
551 //remove blacklisted ASCII ranges of chars - security FIRST - keep only ascii letters, numnbers and spaces
552 //the result should be safe to be used directly in html and SQL
553 $param = preg_replace("/[\\000-\\x1f\\x21-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f]/", '', $param);
554 //now remove some unicode ranges we do not want
555 $param = preg_replace("/[\\x{80}-\\x{bf}\\x{d7}\\x{f7}]/u", '', $param);
556 //cleanup the spaces
557 $param = preg_replace('/ +/', ' ', $param);
558 return trim($param);
559
560 case PARAM_TAGLIST:
561 $tags = explode(',', $param);
562 $result = array();
563 foreach ($tags as $tag) {
564 $res = clean_param($tag, PARAM_TAG);
565 if ($res != '') {
566 $result[] = $res;
567 }
568 }
569 if ($result) {
570 return implode(',', $result);
571 } else {
572 return '';
573 }
574
a083b93c 575 default: // throw error, switched parameters in optional_param or another serious problem
576 error("Unknown parameter type: $type");
2ae28153 577 }
e0d346ff 578}
579
6b174680 580
7a530277 581
7cf1c7bd 582/**
583 * Set a key in global configuration
584 *
89dcb99d 585 * Set a key/value pair in both this session's {@link $CFG} global variable
7cf1c7bd 586 * and in the 'config' database table for future sessions.
e1ecf0a0 587 *
588 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
589 * In that case it doesn't affect $CFG.
7cf1c7bd 590 *
591 * @param string $name the key to set
9cdb766d 592 * @param string $value the value to set (without magic quotes)
a4080313 593 * @param string $plugin (optional) the plugin scope
7cf1c7bd 594 * @uses $CFG
595 * @return bool
596 */
a4080313 597function set_config($name, $value, $plugin=NULL) {
9fa49e22 598/// No need for get_config because they are usually always available in $CFG
70812e39 599
42282810 600 global $CFG;
601
a4080313 602 if (empty($plugin)) {
603 $CFG->$name = $value; // So it's defined for this invocation at least
e1ecf0a0 604
a4080313 605 if (get_field('config', 'name', 'name', $name)) {
9cdb766d 606 return set_field('config', 'value', addslashes($value), 'name', $name);
a4080313 607 } else {
9cdb766d 608 $config = new object();
a4080313 609 $config->name = $name;
9cdb766d 610 $config->value = addslashes($value);
a4080313 611 return insert_record('config', $config);
612 }
613 } else { // plugin scope
614 if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
9cdb766d 615 return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
a4080313 616 } else {
9cdb766d 617 $config = new object();
618 $config->plugin = addslashes($plugin);
a4080313 619 $config->name = $name;
f855cdad 620 $config->value = addslashes($value);
a4080313 621 return insert_record('config_plugins', $config);
622 }
623 }
624}
625
626/**
e1ecf0a0 627 * Get configuration values from the global config table
a4080313 628 * or the config_plugins table.
629 *
630 * If called with no parameters it will do the right thing
631 * generating $CFG safely from the database without overwriting
e1ecf0a0 632 * existing values.
a4080313 633 *
9220fba5 634 * If called with 2 parameters it will return a $string single
635 * value or false of the value is not found.
636 *
e1ecf0a0 637 * @param string $plugin
638 * @param string $name
a4080313 639 * @uses $CFG
640 * @return hash-like object or single value
641 *
642 */
643function get_config($plugin=NULL, $name=NULL) {
7cf1c7bd 644
a4080313 645 global $CFG;
dfc9ba9b 646
a4080313 647 if (!empty($name)) { // the user is asking for a specific value
648 if (!empty($plugin)) {
9220fba5 649 return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
a4080313 650 } else {
9220fba5 651 return get_field('config', 'value', 'name', $name);
a4080313 652 }
653 }
654
655 // the user is after a recordset
656 if (!empty($plugin)) {
657 if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
658 $configs = (array)$configs;
659 $localcfg = array();
660 foreach ($configs as $config) {
661 $localcfg[$config->name] = $config->value;
662 }
663 return (object)$localcfg;
664 } else {
665 return false;
666 }
d897cae4 667 } else {
a4080313 668 // this was originally in setup.php
669 if ($configs = get_records('config')) {
670 $localcfg = (array)$CFG;
671 foreach ($configs as $config) {
672 if (!isset($localcfg[$config->name])) {
673 $localcfg[$config->name] = $config->value;
674 } else {
e1ecf0a0 675 if ($localcfg[$config->name] != $config->value ) {
a4080313 676 // complain if the DB has a different
677 // value than config.php does
678 error_log("\$CFG->{$config->name} in config.php ({$localcfg[$config->name]}) overrides database setting ({$config->value})");
679 }
680 }
681 }
e1ecf0a0 682
a4080313 683 $localcfg = (object)$localcfg;
684 return $localcfg;
685 } else {
686 // preserve $CFG if DB returns nothing or error
687 return $CFG;
688 }
e1ecf0a0 689
39917a09 690 }
39917a09 691}
692
b0270f84 693/**
694 * Removes a key from global configuration
695 *
696 * @param string $name the key to set
697 * @param string $plugin (optional) the plugin scope
698 * @uses $CFG
699 * @return bool
700 */
701function unset_config($name, $plugin=NULL) {
702
703 global $CFG;
704
705 unset($CFG->$name);
706
707 if (empty($plugin)) {
708 return delete_records('config', 'name', $name);
5e623a33 709 } else {
b0270f84 710 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
711 }
712}
713
a4080313 714
7cf1c7bd 715/**
716 * Refresh current $USER session global variable with all their current preferences.
717 * @uses $USER
718 */
70812e39 719function reload_user_preferences() {
70812e39 720
721 global $USER;
722
346c3e2f 723 //reset preference
724 $USER->preference = array();
070e2616 725
346c3e2f 726 if (!isloggedin() or isguestuser()) {
727 // no pernament storage for not-logged-in user and guest
70812e39 728
346c3e2f 729 } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
70812e39 730 foreach ($preferences as $preference) {
731 $USER->preference[$preference->name] = $preference->value;
732 }
c6d15803 733 }
346c3e2f 734
735 return true;
70812e39 736}
737
7cf1c7bd 738/**
739 * Sets a preference for the current user
740 * Optionally, can set a preference for a different user object
741 * @uses $USER
68fbd8e1 742 * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
743
7cf1c7bd 744 * @param string $name The key to set as preference for the specified user
745 * @param string $value The value to set forthe $name key in the specified user's record
346c3e2f 746 * @param int $otheruserid A moodle user ID
bbd3f2c4 747 * @return bool
7cf1c7bd 748 */
346c3e2f 749function set_user_preference($name, $value, $otheruserid=NULL) {
70812e39 750
751 global $USER;
752
346c3e2f 753 if (!isset($USER->preference)) {
754 reload_user_preferences();
d35757eb 755 }
756
70812e39 757 if (empty($name)) {
758 return false;
759 }
760
346c3e2f 761 $nostore = false;
762
763 if (empty($otheruserid)){
764 if (!isloggedin() or isguestuser()) {
765 $nostore = true;
766 }
767 $userid = $USER->id;
768 } else {
769 if (isguestuser($otheruserid)) {
770 $nostore = true;
771 }
772 $userid = $otheruserid;
773 }
774
775 $return = true;
776 if ($nostore) {
777 // no pernament storage for not-logged-in user and guest
778
779 } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
780 if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
781 $return = false;
066af654 782 }
70812e39 783
784 } else {
346c3e2f 785 $preference = new object();
a3f1f815 786 $preference->userid = $userid;
346c3e2f 787 $preference->name = addslashes($name);
788 $preference->value = addslashes((string)$value);
789 if (!insert_record('user_preferences', $preference)) {
790 $return = false;
70812e39 791 }
792 }
346c3e2f 793
794 // update value in USER session if needed
795 if ($userid == $USER->id) {
796 $USER->preference[$name] = (string)$value;
797 }
798
799 return $return;
70812e39 800}
801
6eb3e776 802/**
803 * Unsets a preference completely by deleting it from the database
804 * Optionally, can set a preference for a different user id
805 * @uses $USER
806 * @param string $name The key to unset as preference for the specified user
346c3e2f 807 * @param int $otheruserid A moodle user ID
6eb3e776 808 */
346c3e2f 809function unset_user_preference($name, $otheruserid=NULL) {
6eb3e776 810
811 global $USER;
812
346c3e2f 813 if (!isset($USER->preference)) {
814 reload_user_preferences();
6eb3e776 815 }
816
346c3e2f 817 if (empty($otheruserid)){
818 $userid = $USER->id;
819 } else {
820 $userid = $otheruserid;
821 }
822
823 //Delete the preference from $USER if needed
824 if ($userid == $USER->id) {
49d005ee 825 unset($USER->preference[$name]);
826 }
e1ecf0a0 827
49d005ee 828 //Then from DB
346c3e2f 829 return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
6eb3e776 830}
831
832
7cf1c7bd 833/**
834 * Sets a whole array of preferences for the current user
835 * @param array $prefarray An array of key/value pairs to be set
346c3e2f 836 * @param int $otheruserid A moodle user ID
bbd3f2c4 837 * @return bool
7cf1c7bd 838 */
346c3e2f 839function set_user_preferences($prefarray, $otheruserid=NULL) {
70812e39 840
841 if (!is_array($prefarray) or empty($prefarray)) {
842 return false;
843 }
844
845 $return = true;
846 foreach ($prefarray as $name => $value) {
346c3e2f 847 // The order is important; test for return is done first
848 $return = (set_user_preference($name, $value, $otheruserid) && $return);
70812e39 849 }
850 return $return;
851}
852
7cf1c7bd 853/**
854 * If no arguments are supplied this function will return
361855e6 855 * all of the current user preferences as an array.
7cf1c7bd 856 * If a name is specified then this function
857 * attempts to return that particular preference value. If
858 * none is found, then the optional value $default is returned,
859 * otherwise NULL.
860 * @param string $name Name of the key to use in finding a preference value
861 * @param string $default Value to be returned if the $name key is not set in the user preferences
346c3e2f 862 * @param int $otheruserid A moodle user ID
7cf1c7bd 863 * @uses $USER
864 * @return string
865 */
346c3e2f 866function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
70812e39 867 global $USER;
868
346c3e2f 869 if (!isset($USER->preference)) {
870 reload_user_preferences();
871 }
a3f1f815 872
346c3e2f 873 if (empty($otheruserid)){
874 $userid = $USER->id;
a3f1f815 875 } else {
346c3e2f 876 $userid = $otheruserid;
877 }
a3f1f815 878
346c3e2f 879 if ($userid == $USER->id) {
880 $preference = $USER->preference;
881
882 } else {
883 $preference = array();
884 if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
885 foreach ($prefdata as $pref) {
886 $preference[$pref->name] = $pref->value;
887 }
a3f1f815 888 }
346c3e2f 889 }
890
891 if (empty($name)) {
892 return $preference; // All values
893
894 } else if (array_key_exists($name, $preference)) {
895 return $preference[$name]; // The single value
896
897 } else {
898 return $default; // Default value (or NULL)
70812e39 899 }
70812e39 900}
901
902
9fa49e22 903/// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
39917a09 904
7cf1c7bd 905/**
c6d15803 906 * Given date parts in user time produce a GMT timestamp.
7cf1c7bd 907 *
68fbd8e1 908 * @param int $year The year part to create timestamp of
909 * @param int $month The month part to create timestamp of
910 * @param int $day The day part to create timestamp of
911 * @param int $hour The hour part to create timestamp of
912 * @param int $minute The minute part to create timestamp of
913 * @param int $second The second part to create timestamp of
914 * @param float $timezone ?
915 * @param bool $applydst ?
e34d817e 916 * @return int timestamp
7cf1c7bd 917 * @todo Finish documenting this function
918 */
9f1f6daf 919function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
39917a09 920
dddb014a 921 $timezone = get_user_timezone_offset($timezone);
922
94e34118 923 if (abs($timezone) > 13) {
68fbd8e1 924 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
03c17ddf 925 } else {
68fbd8e1 926 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
196f2619 927 $time = usertime($time, $timezone);
28c66824 928 if($applydst) {
929 $time -= dst_offset_on($time);
930 }
9f1f6daf 931 }
932
196f2619 933 return $time;
85cafb3e 934
39917a09 935}
936
7cf1c7bd 937/**
938 * Given an amount of time in seconds, returns string
5602f7cf 939 * formatted nicely as weeks, days, hours etc as needed
7cf1c7bd 940 *
2f87145b 941 * @uses MINSECS
942 * @uses HOURSECS
943 * @uses DAYSECS
5602f7cf 944 * @uses YEARSECS
c6d15803 945 * @param int $totalsecs ?
946 * @param array $str ?
89dcb99d 947 * @return string
7cf1c7bd 948 */
949 function format_time($totalsecs, $str=NULL) {
c7e3ac2a 950
6b174680 951 $totalsecs = abs($totalsecs);
c7e3ac2a 952
8dbed6be 953 if (!$str) { // Create the str structure the slow way
b0ccd3fb 954 $str->day = get_string('day');
955 $str->days = get_string('days');
956 $str->hour = get_string('hour');
957 $str->hours = get_string('hours');
958 $str->min = get_string('min');
959 $str->mins = get_string('mins');
960 $str->sec = get_string('sec');
961 $str->secs = get_string('secs');
5602f7cf 962 $str->year = get_string('year');
963 $str->years = get_string('years');
8dbed6be 964 }
965
5602f7cf 966
967 $years = floor($totalsecs/YEARSECS);
968 $remainder = $totalsecs - ($years*YEARSECS);
5602f7cf 969 $days = floor($remainder/DAYSECS);
7a5672c9 970 $remainder = $totalsecs - ($days*DAYSECS);
971 $hours = floor($remainder/HOURSECS);
972 $remainder = $remainder - ($hours*HOURSECS);
973 $mins = floor($remainder/MINSECS);
974 $secs = $remainder - ($mins*MINSECS);
8dbed6be 975
976 $ss = ($secs == 1) ? $str->sec : $str->secs;
977 $sm = ($mins == 1) ? $str->min : $str->mins;
978 $sh = ($hours == 1) ? $str->hour : $str->hours;
979 $sd = ($days == 1) ? $str->day : $str->days;
5602f7cf 980 $sy = ($years == 1) ? $str->year : $str->years;
8dbed6be 981
5602f7cf 982 $oyears = '';
b0ccd3fb 983 $odays = '';
984 $ohours = '';
985 $omins = '';
986 $osecs = '';
9c9f7d77 987
5602f7cf 988 if ($years) $oyears = $years .' '. $sy;
b0ccd3fb 989 if ($days) $odays = $days .' '. $sd;
990 if ($hours) $ohours = $hours .' '. $sh;
991 if ($mins) $omins = $mins .' '. $sm;
992 if ($secs) $osecs = $secs .' '. $ss;
6b174680 993
77ac808e 994 if ($years) return trim($oyears .' '. $odays);
995 if ($days) return trim($odays .' '. $ohours);
996 if ($hours) return trim($ohours .' '. $omins);
997 if ($mins) return trim($omins .' '. $osecs);
b0ccd3fb 998 if ($secs) return $osecs;
999 return get_string('now');
6b174680 1000}
f9903ed0 1001
7cf1c7bd 1002/**
1003 * Returns a formatted string that represents a date in user time
1004 * <b>WARNING: note that the format is for strftime(), not date().</b>
1005 * Because of a bug in most Windows time libraries, we can't use
1006 * the nicer %e, so we have to use %d which has leading zeroes.
1007 * A lot of the fuss in the function is just getting rid of these leading
1008 * zeroes as efficiently as possible.
361855e6 1009 *
8c3dba73 1010 * If parameter fixday = true (default), then take off leading
7cf1c7bd 1011 * zero from %d, else mantain it.
1012 *
2f87145b 1013 * @uses HOURSECS
e34d817e 1014 * @param int $date timestamp in GMT
1015 * @param string $format strftime format
d2a9f7cc 1016 * @param float $timezone
bbd3f2c4 1017 * @param bool $fixday If true (default) then the leading
c6d15803 1018 * zero from %d is removed. If false then the leading zero is mantained.
1019 * @return string
7cf1c7bd 1020 */
b0ccd3fb 1021function userdate($date, $format='', $timezone=99, $fixday = true) {
7a302afc 1022
1ac7ee24 1023 global $CFG;
1024
1306c5ea 1025 if (empty($format)) {
1026 $format = get_string('strftimedaydatetime');
5fa51a39 1027 }
035cdbff 1028
c3a3c5b8 1029 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
1030 $fixday = false;
1031 } else if ($fixday) {
1032 $formatnoday = str_replace('%d', 'DD', $format);
61ae5d36 1033 $fixday = ($formatnoday != $format);
1034 }
dcde9f02 1035
88ec5b7c 1036 $date += dst_offset_on($date);
85351042 1037
494b9296 1038 $timezone = get_user_timezone_offset($timezone);
102dc313 1039
1040 if (abs($timezone) > 13) { /// Server time
d2a9f7cc 1041 if ($fixday) {
102dc313 1042 $datestring = strftime($formatnoday, $date);
1043 $daystring = str_replace(' 0', '', strftime(' %d', $date));
1044 $datestring = str_replace('DD', $daystring, $datestring);
1045 } else {
1046 $datestring = strftime($format, $date);
1047 }
88ec5b7c 1048 } else {
102dc313 1049 $date += (int)($timezone * 3600);
1050 if ($fixday) {
1051 $datestring = gmstrftime($formatnoday, $date);
1052 $daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
1053 $datestring = str_replace('DD', $daystring, $datestring);
1054 } else {
1055 $datestring = gmstrftime($format, $date);
1056 }
88ec5b7c 1057 }
102dc313 1058
fb773106 1059/// If we are running under Windows convert from windows encoding to UTF-8
1060/// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
11f7b25d 1061
fb773106 1062 if ($CFG->ostype == 'WINDOWS') {
11f7b25d 1063 if ($localewincharset = get_string('localewincharset')) {
1064 $textlib = textlib_get_instance();
810944af 1065 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
11f7b25d 1066 }
1067 }
1068
035cdbff 1069 return $datestring;
873960de 1070}
1071
7cf1c7bd 1072/**
196f2619 1073 * Given a $time timestamp in GMT (seconds since epoch),
c6d15803 1074 * returns an array that represents the date in user time
7cf1c7bd 1075 *
2f87145b 1076 * @uses HOURSECS
196f2619 1077 * @param int $time Timestamp in GMT
68fbd8e1 1078 * @param float $timezone ?
c6d15803 1079 * @return array An array that represents the date in user time
7cf1c7bd 1080 * @todo Finish documenting this function
1081 */
196f2619 1082function usergetdate($time, $timezone=99) {
6b174680 1083
494b9296 1084 $timezone = get_user_timezone_offset($timezone);
a36166d3 1085
e34d817e 1086 if (abs($timezone) > 13) { // Server time
ed1f69b0 1087 return getdate($time);
d2a9f7cc 1088 }
1089
e34d817e 1090 // There is no gmgetdate so we use gmdate instead
02f0527d 1091 $time += dst_offset_on($time);
e34d817e 1092 $time += intval((float)$timezone * HOURSECS);
3bba1e6e 1093
1094 $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
02f0527d 1095
9f1f6daf 1096 list(
1097 $getdate['seconds'],
1098 $getdate['minutes'],
1099 $getdate['hours'],
1100 $getdate['mday'],
1101 $getdate['mon'],
1102 $getdate['year'],
1103 $getdate['wday'],
1104 $getdate['yday'],
1105 $getdate['weekday'],
1106 $getdate['month']
3bba1e6e 1107 ) = explode('_', $datestring);
9f1f6daf 1108
d2d6171f 1109 return $getdate;
d552ead0 1110}
1111
7cf1c7bd 1112/**
1113 * Given a GMT timestamp (seconds since epoch), offsets it by
1114 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1115 *
2f87145b 1116 * @uses HOURSECS
c6d15803 1117 * @param int $date Timestamp in GMT
e34d817e 1118 * @param float $timezone
c6d15803 1119 * @return int
7cf1c7bd 1120 */
d552ead0 1121function usertime($date, $timezone=99) {
a36166d3 1122
494b9296 1123 $timezone = get_user_timezone_offset($timezone);
2665e47a 1124
0431bd7c 1125 if (abs($timezone) > 13) {
d552ead0 1126 return $date;
1127 }
7a5672c9 1128 return $date - (int)($timezone * HOURSECS);
d552ead0 1129}
1130
8c3dba73 1131/**
1132 * Given a time, return the GMT timestamp of the most recent midnight
1133 * for the current user.
1134 *
e34d817e 1135 * @param int $date Timestamp in GMT
1136 * @param float $timezone ?
c6d15803 1137 * @return ?
8c3dba73 1138 */
edf7fe8c 1139function usergetmidnight($date, $timezone=99) {
edf7fe8c 1140
494b9296 1141 $timezone = get_user_timezone_offset($timezone);
edf7fe8c 1142 $userdate = usergetdate($date, $timezone);
4606d9bb 1143
02f0527d 1144 // Time of midnight of this user's day, in GMT
1145 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
edf7fe8c 1146
1147}
1148
7cf1c7bd 1149/**
1150 * Returns a string that prints the user's timezone
1151 *
1152 * @param float $timezone The user's timezone
1153 * @return string
1154 */
d552ead0 1155function usertimezone($timezone=99) {
d552ead0 1156
0c244315 1157 $tz = get_user_timezone($timezone);
f30fe8d0 1158
0c244315 1159 if (!is_float($tz)) {
1160 return $tz;
d552ead0 1161 }
0c244315 1162
1163 if(abs($tz) > 13) { // Server time
1164 return get_string('serverlocaltime');
1165 }
1166
1167 if($tz == intval($tz)) {
1168 // Don't show .0 for whole hours
1169 $tz = intval($tz);
1170 }
1171
1172 if($tz == 0) {
b0ccd3fb 1173 return 'GMT';
d552ead0 1174 }
0c244315 1175 else if($tz > 0) {
1176 return 'GMT+'.$tz;
1177 }
1178 else {
1179 return 'GMT'.$tz;
d552ead0 1180 }
e1ecf0a0 1181
f9903ed0 1182}
1183
7cf1c7bd 1184/**
1185 * Returns a float which represents the user's timezone difference from GMT in hours
1186 * Checks various settings and picks the most dominant of those which have a value
1187 *
7cf1c7bd 1188 * @uses $CFG
1189 * @uses $USER
b2b68362 1190 * @param float $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
c6d15803 1191 * @return int
7cf1c7bd 1192 */
494b9296 1193function get_user_timezone_offset($tz = 99) {
f30fe8d0 1194
43b59916 1195 global $USER, $CFG;
1196
e8904995 1197 $tz = get_user_timezone($tz);
c9e55a25 1198
7b9e355e 1199 if (is_float($tz)) {
1200 return $tz;
1201 } else {
e8904995 1202 $tzrecord = get_timezone_record($tz);
7b9e355e 1203 if (empty($tzrecord)) {
e8904995 1204 return 99.0;
1205 }
4f2dbde9 1206 return (float)$tzrecord->gmtoff / HOURMINS;
e8904995 1207 }
1208}
1209
bbd3f2c4 1210/**
b2b68362 1211 * Returns a float or a string which denotes the user's timezone
1212 * A float value means that a simple offset from GMT is used, while a string (it will be the name of a timezone in the database)
1213 * means that for this timezone there are also DST rules to be taken into account
1214 * Checks various settings and picks the most dominant of those which have a value
bbd3f2c4 1215 *
1216 * @uses $USER
1217 * @uses $CFG
b2b68362 1218 * @param float $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
1219 * @return mixed
bbd3f2c4 1220 */
e8904995 1221function get_user_timezone($tz = 99) {
1222 global $USER, $CFG;
43b59916 1223
f30fe8d0 1224 $timezones = array(
e8904995 1225 $tz,
1226 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
43b59916 1227 isset($USER->timezone) ? $USER->timezone : 99,
1228 isset($CFG->timezone) ? $CFG->timezone : 99,
f30fe8d0 1229 );
43b59916 1230
e8904995 1231 $tz = 99;
43b59916 1232
e8904995 1233 while(($tz == '' || $tz == 99) && $next = each($timezones)) {
1234 $tz = $next['value'];
43b59916 1235 }
e8904995 1236
1237 return is_numeric($tz) ? (float) $tz : $tz;
43b59916 1238}
1239
bbd3f2c4 1240/**
1241 * ?
1242 *
1243 * @uses $CFG
1244 * @uses $db
1245 * @param string $timezonename ?
1246 * @return object
1247 */
43b59916 1248function get_timezone_record($timezonename) {
1249 global $CFG, $db;
1250 static $cache = NULL;
1251
8edffd15 1252 if ($cache === NULL) {
43b59916 1253 $cache = array();
1254 }
1255
8edffd15 1256 if (isset($cache[$timezonename])) {
43b59916 1257 return $cache[$timezonename];
f30fe8d0 1258 }
1259
952d8dc8 1260 return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1261 WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
f30fe8d0 1262}
f9903ed0 1263
bbd3f2c4 1264/**
1265 * ?
1266 *
1267 * @uses $CFG
1268 * @uses $USER
1269 * @param ? $fromyear ?
1270 * @param ? $to_year ?
1271 * @return bool
1272 */
830a2bbd 1273function calculate_user_dst_table($from_year = NULL, $to_year = NULL) {
2280ecf5 1274 global $CFG, $SESSION;
85cafb3e 1275
989585e9 1276 $usertz = get_user_timezone();
7cb29a3d 1277
989585e9 1278 if (is_float($usertz)) {
1279 // Trivial timezone, no DST
1280 return false;
1281 }
1282
2280ecf5 1283 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
989585e9 1284 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2280ecf5 1285 unset($SESSION->dst_offsets);
1286 unset($SESSION->dst_range);
830a2bbd 1287 }
1288
2280ecf5 1289 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
830a2bbd 1290 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1291 // This will be the return path most of the time, pretty light computationally
1292 return true;
85cafb3e 1293 }
1294
830a2bbd 1295 // Reaching here means we either need to extend our table or create it from scratch
989585e9 1296
1297 // Remember which TZ we calculated these changes for
2280ecf5 1298 $SESSION->dst_offsettz = $usertz;
989585e9 1299
2280ecf5 1300 if(empty($SESSION->dst_offsets)) {
830a2bbd 1301 // If we 're creating from scratch, put the two guard elements in there
2280ecf5 1302 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
830a2bbd 1303 }
2280ecf5 1304 if(empty($SESSION->dst_range)) {
830a2bbd 1305 // If creating from scratch
1306 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1307 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
1308
1309 // Fill in the array with the extra years we need to process
1310 $yearstoprocess = array();
1311 for($i = $from; $i <= $to; ++$i) {
1312 $yearstoprocess[] = $i;
1313 }
1314
1315 // Take note of which years we have processed for future calls
2280ecf5 1316 $SESSION->dst_range = array($from, $to);
830a2bbd 1317 }
1318 else {
1319 // If needing to extend the table, do the same
1320 $yearstoprocess = array();
1321
2280ecf5 1322 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1323 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
830a2bbd 1324
2280ecf5 1325 if($from < $SESSION->dst_range[0]) {
830a2bbd 1326 // Take note of which years we need to process and then note that we have processed them for future calls
2280ecf5 1327 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
830a2bbd 1328 $yearstoprocess[] = $i;
1329 }
2280ecf5 1330 $SESSION->dst_range[0] = $from;
830a2bbd 1331 }
2280ecf5 1332 if($to > $SESSION->dst_range[1]) {
830a2bbd 1333 // Take note of which years we need to process and then note that we have processed them for future calls
2280ecf5 1334 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
830a2bbd 1335 $yearstoprocess[] = $i;
1336 }
2280ecf5 1337 $SESSION->dst_range[1] = $to;
830a2bbd 1338 }
1339 }
1340
1341 if(empty($yearstoprocess)) {
1342 // This means that there was a call requesting a SMALLER range than we have already calculated
1343 return true;
1344 }
1345
1346 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1347 // Also, the array is sorted in descending timestamp order!
1348
1349 // Get DB data
989585e9 1350 $presetrecords = get_records('timezone', 'name', $usertz, 'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, std_startday, std_weekday, std_skipweeks, std_time');
e789650d 1351 if(empty($presetrecords)) {
1352 return false;
1353 }
57f1191c 1354
830a2bbd 1355 // Remove ending guard (first element of the array)
2280ecf5 1356 reset($SESSION->dst_offsets);
1357 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
830a2bbd 1358
1359 // Add all required change timestamps
1360 foreach($yearstoprocess as $y) {
1361 // Find the record which is in effect for the year $y
1362 foreach($presetrecords as $year => $preset) {
1363 if($year <= $y) {
1364 break;
c9e72798 1365 }
830a2bbd 1366 }
1367
1368 $changes = dst_changes_for_year($y, $preset);
1369
1370 if($changes === NULL) {
1371 continue;
1372 }
1373 if($changes['dst'] != 0) {
2280ecf5 1374 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
830a2bbd 1375 }
1376 if($changes['std'] != 0) {
2280ecf5 1377 $SESSION->dst_offsets[$changes['std']] = 0;
c9e72798 1378 }
85cafb3e 1379 }
42d36497 1380
830a2bbd 1381 // Put in a guard element at the top
2280ecf5 1382 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1383 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
830a2bbd 1384
1385 // Sort again
2280ecf5 1386 krsort($SESSION->dst_offsets);
830a2bbd 1387
e789650d 1388 return true;
1389}
42d36497 1390
e789650d 1391function dst_changes_for_year($year, $timezone) {
7cb29a3d 1392
e789650d 1393 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1394 return NULL;
42d36497 1395 }
7cb29a3d 1396
e789650d 1397 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1398 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1399
1400 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1401 list($std_hour, $std_min) = explode(':', $timezone->std_time);
d2a9f7cc 1402
6dc8dddc 1403 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1404 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
830a2bbd 1405
1406 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1407 // This has the advantage of being able to have negative values for hour, i.e. for timezones
1408 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1409
1410 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1411 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
42d36497 1412
e789650d 1413 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
42d36497 1414}
1415
02f0527d 1416// $time must NOT be compensated at all, it has to be a pure timestamp
1417function dst_offset_on($time) {
2280ecf5 1418 global $SESSION;
02f0527d 1419
2280ecf5 1420 if(!calculate_user_dst_table() || empty($SESSION->dst_offsets)) {
c9e72798 1421 return 0;
85cafb3e 1422 }
1423
2280ecf5 1424 reset($SESSION->dst_offsets);
1425 while(list($from, $offset) = each($SESSION->dst_offsets)) {
59556d48 1426 if($from <= $time) {
c9e72798 1427 break;
1428 }
1429 }
1430
830a2bbd 1431 // This is the normal return path
1432 if($offset !== NULL) {
1433 return $offset;
02f0527d 1434 }
02f0527d 1435
830a2bbd 1436 // Reaching this point means we haven't calculated far enough, do it now:
1437 // Calculate extra DST changes if needed and recurse. The recursion always
1438 // moves toward the stopping condition, so will always end.
1439
1440 if($from == 0) {
2280ecf5 1441 // We need a year smaller than $SESSION->dst_range[0]
1442 if($SESSION->dst_range[0] == 1971) {
830a2bbd 1443 return 0;
1444 }
2280ecf5 1445 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL);
830a2bbd 1446 return dst_offset_on($time);
1447 }
1448 else {
2280ecf5 1449 // We need a year larger than $SESSION->dst_range[1]
1450 if($SESSION->dst_range[1] == 2035) {
830a2bbd 1451 return 0;
1452 }
2280ecf5 1453 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5);
830a2bbd 1454 return dst_offset_on($time);
1455 }
85cafb3e 1456}
02f0527d 1457
28902d99 1458function find_day_in_month($startday, $weekday, $month, $year) {
8dc3f6cf 1459
1460 $daysinmonth = days_in_month($month, $year);
1461
42d36497 1462 if($weekday == -1) {
28902d99 1463 // Don't care about weekday, so return:
1464 // abs($startday) if $startday != -1
1465 // $daysinmonth otherwise
1466 return ($startday == -1) ? $daysinmonth : abs($startday);
8dc3f6cf 1467 }
1468
1469 // From now on we 're looking for a specific weekday
8dc3f6cf 1470
28902d99 1471 // Give "end of month" its actual value, since we know it
1472 if($startday == -1) {
1473 $startday = -1 * $daysinmonth;
1474 }
1475
1476 // Starting from day $startday, the sign is the direction
8dc3f6cf 1477
28902d99 1478 if($startday < 1) {
8dc3f6cf 1479
28902d99 1480 $startday = abs($startday);
8dc3f6cf 1481 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1482
1483 // This is the last such weekday of the month
1484 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1485 if($lastinmonth > $daysinmonth) {
1486 $lastinmonth -= 7;
42d36497 1487 }
8dc3f6cf 1488
28902d99 1489 // Find the first such weekday <= $startday
1490 while($lastinmonth > $startday) {
8dc3f6cf 1491 $lastinmonth -= 7;
42d36497 1492 }
8dc3f6cf 1493
1494 return $lastinmonth;
e1ecf0a0 1495
42d36497 1496 }
1497 else {
42d36497 1498
28902d99 1499 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
42d36497 1500
8dc3f6cf 1501 $diff = $weekday - $indexweekday;
1502 if($diff < 0) {
1503 $diff += 7;
42d36497 1504 }
42d36497 1505
28902d99 1506 // This is the first such weekday of the month equal to or after $startday
1507 $firstfromindex = $startday + $diff;
42d36497 1508
8dc3f6cf 1509 return $firstfromindex;
1510
1511 }
42d36497 1512}
1513
bbd3f2c4 1514/**
1515 * Calculate the number of days in a given month
1516 *
1517 * @param int $month The month whose day count is sought
1518 * @param int $year The year of the month whose day count is sought
1519 * @return int
1520 */
42d36497 1521function days_in_month($month, $year) {
1522 return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1523}
1524
bbd3f2c4 1525/**
1526 * Calculate the position in the week of a specific calendar day
1527 *
1528 * @param int $day The day of the date whose position in the week is sought
1529 * @param int $month The month of the date whose position in the week is sought
1530 * @param int $year The year of the date whose position in the week is sought
1531 * @return int
1532 */
8dc3f6cf 1533function dayofweek($day, $month, $year) {
1534 // I wonder if this is any different from
1535 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1536 return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1537}
1538
9fa49e22 1539/// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
f9903ed0 1540
bbd3f2c4 1541/**
1542 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1543 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1544 * sesskey string if $USER exists, or boolean false if not.
1545 *
1546 * @uses $USER
1547 * @return string
1548 */
04280e85 1549function sesskey() {
1a33f699 1550 global $USER;
1551
1552 if(!isset($USER)) {
1553 return false;
1554 }
1555
1556 if (empty($USER->sesskey)) {
1557 $USER->sesskey = random_string(10);
1558 }
1559
1560 return $USER->sesskey;
1561}
1562
0302c52f 1563
c4d0753b 1564/**
1565 * For security purposes, this function will check that the currently
1566 * given sesskey (passed as a parameter to the script or this function)
1567 * matches that of the current user.
1568 *
1569 * @param string $sesskey optionally provided sesskey
1570 * @return bool
1571 */
1572function confirm_sesskey($sesskey=NULL) {
1573 global $USER;
0302c52f 1574
c4d0753b 1575 if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1576 return true;
0302c52f 1577 }
1578
c4d0753b 1579 if (empty($sesskey)) {
1580 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
0302c52f 1581 }
1582
c4d0753b 1583 if (!isset($USER->sesskey)) {
1584 return false;
1585 }
0302c52f 1586
c4d0753b 1587 return ($USER->sesskey === $sesskey);
0302c52f 1588}
c4d0753b 1589
dcf6d93c 1590/**
9152fc99 1591 * Setup all global $CFG course variables, set locale and also themes
1592 * This function can be used on pages that do not require login instead of require_login()
1593 *
dcf6d93c 1594 * @param mixed $courseorid id of the course or course object
1595 */
1596function course_setup($courseorid=0) {
1306c5ea 1597 global $COURSE, $CFG, $SITE;
dcf6d93c 1598
1599/// Redefine global $COURSE if needed
1600 if (empty($courseorid)) {
1601 // no change in global $COURSE - for backwards compatibiltiy
5e623a33 1602 // if require_rogin() used after require_login($courseid);
dcf6d93c 1603 } else if (is_object($courseorid)) {
1604 $COURSE = clone($courseorid);
1605 } else {
1606 global $course; // used here only to prevent repeated fetching from DB - may be removed later
9152fc99 1607 if (!empty($course->id) and $course->id == SITEID) {
1608 $COURSE = clone($SITE);
1609 } else if (!empty($course->id) and $course->id == $courseorid) {
dcf6d93c 1610 $COURSE = clone($course);
1611 } else {
1612 if (!$COURSE = get_record('course', 'id', $courseorid)) {
1613 error('Invalid course ID');
1614 }
1615 }
1616 }
1617
9152fc99 1618/// set locale and themes
dcf6d93c 1619 moodle_setlocale();
dcf6d93c 1620 theme_setup();
1621
dcf6d93c 1622}
c4d0753b 1623
7cf1c7bd 1624/**
ec81373f 1625 * This function checks that the current user is logged in and has the
1626 * required privileges
1627 *
7cf1c7bd 1628 * This function checks that the current user is logged in, and optionally
ec81373f 1629 * whether they are allowed to be in a particular course and view a particular
1630 * course module.
1631 * If they are not logged in, then it redirects them to the site login unless
d2a9f7cc 1632 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
ec81373f 1633 * case they are automatically logged in as guests.
1634 * If $courseid is given and the user is not enrolled in that course then the
1635 * user is redirected to the course enrolment page.
1636 * If $cm is given and the coursemodule is hidden and the user is not a teacher
1637 * in the course then the user is redirected to the course home page.
7cf1c7bd 1638 *
7cf1c7bd 1639 * @uses $CFG
c6d15803 1640 * @uses $SESSION
7cf1c7bd 1641 * @uses $USER
1642 * @uses $FULLME
c6d15803 1643 * @uses SITEID
f07fa644 1644 * @uses $COURSE
33ebaf7c 1645 * @param mixed $courseorid id of the course or course object
bbd3f2c4 1646 * @param bool $autologinguest
1647 * @param object $cm course module object
7cf1c7bd 1648 */
33ebaf7c 1649function require_login($courseorid=0, $autologinguest=true, $cm=null) {
f9903ed0 1650
083c3743 1651 global $CFG, $SESSION, $USER, $COURSE, $FULLME;
d8ba183c 1652
083c3743 1653/// setup global $COURSE, themes, language and locale
dcf6d93c 1654 course_setup($courseorid);
be933850 1655
1845f8b8 1656/// If the user is not even logged in yet then make sure they are
083c3743 1657 if (!isloggedin()) {
1658 //NOTE: $USER->site check was obsoleted by session test cookie,
1659 // $USER->confirmed test is in login/index.php
f9903ed0 1660 $SESSION->wantsurl = $FULLME;
b0ccd3fb 1661 if (!empty($_SERVER['HTTP_REFERER'])) {
1662 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
9f44d972 1663 }
ad56b737 1664 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
8e8d0524 1665 $loginguest = '?loginguest=true';
1666 } else {
1667 $loginguest = '';
a2ebe6a5 1668 }
2c040c29 1669 if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
b0ccd3fb 1670 redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
8a33e371 1671 } else {
2c3432e6 1672 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
083c3743 1673 redirect($wwwroot .'/login/index.php');
8a33e371 1674 }
20fde7b1 1675 exit;
f9903ed0 1676 }
808a3baa 1677
f6f66b03 1678/// loginas as redirection if needed
1679 if ($COURSE->id != SITEID and !empty($USER->realuser)) {
1680 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
1681 if ($USER->loginascontext->instanceid != $COURSE->id) {
3887fe4a 1682 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
5e623a33 1683 }
f6f66b03 1684 }
1685 }
1686
1687
5602f7cf 1688/// check whether the user should be changing password (but only if it is REALLY them)
03d820c7 1689 $userauth = get_auth_plugin($USER->auth);
346c3e2f 1690 if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
03d820c7 1691 if ($userauth->can_change_password()) {
20fde7b1 1692 $SESSION->wantsurl = $FULLME;
80274abf 1693 if ($changeurl = $userauth->change_password_url()) {
9696bd89 1694 //use plugin custom url
80274abf 1695 redirect($changeurl);
1437f0a5 1696 } else {
9696bd89 1697 //use moodle internal method
1698 if (empty($CFG->loginhttps)) {
1699 redirect($CFG->wwwroot .'/login/change_password.php');
1700 } else {
1701 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1702 redirect($wwwroot .'/login/change_password.php');
1703 }
1437f0a5 1704 }
d35757eb 1705 } else {
4fa94a56 1706 error(get_string('nopasswordchangeforced', 'auth'));
d35757eb 1707 }
1708 }
083c3743 1709
1845f8b8 1710/// Check that the user account is properly set up
808a3baa 1711 if (user_not_fully_set_up($USER)) {
20fde7b1 1712 $SESSION->wantsurl = $FULLME;
b0ccd3fb 1713 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
808a3baa 1714 }
d8ba183c 1715
1845f8b8 1716/// Make sure current IP matches the one for this session (if required)
361855e6 1717 if (!empty($CFG->tracksessionip)) {
366dfa60 1718 if ($USER->sessionIP != md5(getremoteaddr())) {
1719 error(get_string('sessionipnomatch', 'error'));
1720 }
1721 }
6d8f47d6 1722
1845f8b8 1723/// Make sure the USER has a sesskey set up. Used for checking script parameters.
04280e85 1724 sesskey();
366dfa60 1725
027a1604 1726 // Check that the user has agreed to a site policy if there is one
1727 if (!empty($CFG->sitepolicy)) {
1728 if (!$USER->policyagreed) {
957b5198 1729 $SESSION->wantsurl = $FULLME;
027a1604 1730 redirect($CFG->wwwroot .'/user/policy.php');
027a1604 1731 }
1695b680 1732 }
1733
1845f8b8 1734/// If the site is currently under maintenance, then print a message
f8e3d5f0 1735 if (!has_capability('moodle/site:config',get_context_instance(CONTEXT_SYSTEM))) {
eeefd0b0 1736 if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1695b680 1737 print_maintenance_message();
20fde7b1 1738 exit;
1695b680 1739 }
027a1604 1740 }
1741
f8e3d5f0 1742/// groupmembersonly access control
1743 if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
1744 if (isguestuser() or !groups_has_membership($cm)) {
1745 error(get_string('groupmembersonlyerror', 'group'), $CFG->wwwroot.'/course/view.php?id='.$cm->course);
1746 }
1747 }
1845f8b8 1748
33ebaf7c 1749 if ($COURSE->id == SITEID) {
1750/// We can eliminate hidden site activities straight away
5e623a33 1751 if (!empty($cm) && !$cm->visible and !has_capability('moodle/course:viewhiddenactivities',
f8e3d5f0 1752 get_context_instance(CONTEXT_SYSTEM))) {
33ebaf7c 1753 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
e3512050 1754 }
33ebaf7c 1755 return;
881a77bf 1756
5e623a33 1757 } else {
33ebaf7c 1758/// Check if the user can be in a particular course
1759 if (!$context = get_context_instance(CONTEXT_COURSE, $COURSE->id)) {
1845f8b8 1760 print_error('nocontext');
1761 }
1762
881a77bf 1763 if (empty($USER->switchrole[$context->id]) &&
33ebaf7c 1764 !($COURSE->visible && course_parent_visible($COURSE)) &&
1765 !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $COURSE->id)) ){
881a77bf 1766 print_header_simple();
1767 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
5e623a33 1768 }
1769
f71346e2 1770 /// Non-guests who don't currently have access, check if they can be allowed in as a guest
1771
1772 if ($USER->username != 'guest' and !has_capability('moodle/course:view', $context)) {
33ebaf7c 1773 if ($COURSE->guest == 1) {
eef879ec 1774 // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
1775 has_capability('clearcache'); // Must clear cache
1776 $guestcaps = get_role_context_caps($CFG->guestroleid, $context);
1777 $USER->capabilities = merge_role_caps($USER->capabilities, $guestcaps);
f71346e2 1778 }
1779 }
1780
1845f8b8 1781 /// If the user is a guest then treat them according to the course policy about guests
1782
1783 if (has_capability('moodle/legacy:guest', $context, NULL, false)) {
33ebaf7c 1784 switch ($COURSE->guest) { /// Check course policy about guest access
1845f8b8 1785
5e623a33 1786 case 1: /// Guests always allowed
1845f8b8 1787 if (!has_capability('moodle/course:view', $context)) { // Prohibited by capability
1788 print_header_simple();
6ba65fa0 1789 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
1845f8b8 1790 }
1791 if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
5e623a33 1792 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
1845f8b8 1793 get_string('activityiscurrentlyhidden'));
1794 }
1795
1796 return; // User is allowed to see this course
1797
1798 break;
1799
5e623a33 1800 case 2: /// Guests allowed with key
33ebaf7c 1801 if (!empty($USER->enrolkey[$COURSE->id])) { // Set by enrol/manual/enrol.php
b1f318a6 1802 return true;
1803 }
1804 // otherwise drop through to logic below (--> enrol.php)
1845f8b8 1805 break;
bbbf2d40 1806
1845f8b8 1807 default: /// Guests not allowed
0be6f678 1808 $strloggedinasguest = get_string('loggedinasguest');
1809 print_header_simple('', '',
1810 build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
21596567 1811 if (empty($USER->switchrole[$context->id])) { // Normal guest
6ba65fa0 1812 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
21596567 1813 } else {
6ba65fa0 1814 notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
33ebaf7c 1815 echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
1816 print_footer($COURSE);
21596567 1817 exit;
1818 }
1845f8b8 1819 break;
1820 }
1821
1822 /// For non-guests, check if they have course view access
1823
1824 } else if (has_capability('moodle/course:view', $context)) {
1825 if (!empty($USER->realuser)) { // Make sure the REAL person can also access this course
d02eeded 1826 if (!has_capability('moodle/course:view', $context, $USER->realuser)) {
1845f8b8 1827 print_header_simple();
b0ccd3fb 1828 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
cb909d74 1829 }
3ce2f1e0 1830 }
1845f8b8 1831
1832 /// Make sure they can read this activity too, if specified
1833
5e623a33 1834 if (!empty($cm) and !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) {
ec81373f 1835 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1836 }
1845f8b8 1837 return; // User is allowed to see this course
1838
da5c172a 1839 }
f9903ed0 1840
9ca3b4f3 1841
1845f8b8 1842 /// Currently not enrolled in the course, so see if they want to enrol
da5c172a 1843 $SESSION->wantsurl = $FULLME;
33ebaf7c 1844 redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
da5c172a 1845 die;
1846 }
f9903ed0 1847}
1848
c4d0753b 1849
1850
1851/**
1852 * This function just makes sure a user is logged out.
1853 *
1854 * @uses $CFG
1855 * @uses $USER
1856 */
1857function require_logout() {
1858
2d4beaff 1859 global $USER, $CFG, $SESSION;
c4d0753b 1860
111e2360 1861 if (isloggedin()) {
c4d0753b 1862 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
1863
533f7910 1864 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
1865 foreach($authsequence as $authname) {
1866 $authplugin = get_auth_plugin($authname);
1867 $authplugin->prelogout_hook();
81693ac7 1868 }
c4d0753b 1869 }
1870
1871 if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
1872 // This method is just to try to avoid silly warnings from PHP 4.3.0
1873 session_unregister("USER");
1874 session_unregister("SESSION");
1875 }
1876
f8bd7030 1877 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
1878 $file = $line = null;
1879 if (headers_sent($file, $line)) {
1880 error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
1881 error_log('Headers were already sent in file: '.$file.' on line '.$line);
1882 } else {
1883 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath);
1884 }
1885
c4d0753b 1886 unset($_SESSION['USER']);
1887 unset($_SESSION['SESSION']);
1888
1889 unset($SESSION);
1890 unset($USER);
1891
1892}
1893
7cf1c7bd 1894/**
1895 * This is a weaker version of {@link require_login()} which only requires login
1896 * when called from within a course rather than the site page, unless
1897 * the forcelogin option is turned on.
1898 *
1899 * @uses $CFG
33ebaf7c 1900 * @param mixed $courseorid The course object or id in question
bbd3f2c4 1901 * @param bool $autologinguest Allow autologin guests if that is wanted
4febb58f 1902 * @param object $cm Course activity module if known
7cf1c7bd 1903 */
33ebaf7c 1904function require_course_login($courseorid, $autologinguest=true, $cm=null) {
f950af3c 1905 global $CFG;
1596edff 1906 if (!empty($CFG->forcelogin)) {
33ebaf7c 1907 // login required for both SITE and courses
1908 require_login($courseorid, $autologinguest, $cm);
63c9ee99 1909
1910 } else if (!empty($cm) and !$cm->visible) {
1911 // always login for hidden activities
1912 require_login($courseorid, $autologinguest, $cm);
1913
39de90ac 1914 } else if ((is_object($courseorid) and $courseorid->id == SITEID)
1915 or (!is_object($courseorid) and $courseorid == SITEID)) {
33ebaf7c 1916 //login for SITE not required
63c9ee99 1917 return;
1918
33ebaf7c 1919 } else {
1920 // course login always required
1921 require_login($courseorid, $autologinguest, $cm);
f950af3c 1922 }
1923}
1924
61c6071f 1925/**
1926 * Require key login. Function terminates with error if key not found or incorrect.
1927 * @param string $script unique script identifier
1928 * @param int $instance optional instance id
1929 */
1930function require_user_key_login($script, $instance=null) {
1931 global $nomoodlecookie, $USER, $SESSION;
1932
1933 if (empty($nomoodlecookie)) {
1934 error('Incorrect use of require_key_login() - session cookies must be disabled!');
1935 }
1936
1937/// extra safety
1938 @session_write_close();
1939
1940 $keyvalue = required_param('key', PARAM_ALPHANUM);
1941
1942 if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
1943 error('Incorrect key');
1944 }
1945
1946 if (!empty($key->validuntil) and $key->validuntil < time()) {
1947 error('Expired key');
1948 }
1949
e436033f 1950 if ($key->iprestriction) {
1951 $remoteaddr = getremoteaddr();
1952 if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
1953 error('Client IP address mismatch');
1954 }
61c6071f 1955 }
1956
1957 if (!$user = get_record('user', 'id', $key->userid)) {
1958 error('Incorrect user record');
1959 }
1960
1961/// emulate normal session
1962 $SESSION = new object();
1963 $USER = $user;
1964
1965/// return isntance id - it might be empty
1966 return $key->instance;
1967}
1968
1969/**
1970 * Creates a new private user access key.
1971 * @param string $script unique target identifier
1972 * @param int $userid
1973 * @param instance $int optional instance id
1974 * @param string $iprestriction optional ip restricted access
1975 * @param timestamp $validuntil key valid only until given data
1976 * @return string access key value
1977 */
1978function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
1979 $key = new object();
1980 $key->script = $script;
1981 $key->userid = $userid;
1982 $key->instance = $instance;
1983 $key->iprestriction = $iprestriction;
1984 $key->validuntil = $validuntil;
1985 $key->timecreated = time();
1986
1987 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
1988 while (record_exists('user_private_key', 'value', $key->value)) {
1989 // must be unique
1990 $key->value = md5($userid.'_'.time().random_string(40));
1991 }
1992
1993 if (!insert_record('user_private_key', $key)) {
1994 error('Can not insert new key');
1995 }
1996
1997 return $key->value;
1998}
1999
7cf1c7bd 2000/**
2001 * Modify the user table by setting the currently logged in user's
2002 * last login to now.
2003 *
2004 * @uses $USER
bbd3f2c4 2005 * @return bool
7cf1c7bd 2006 */
1d881d92 2007function update_user_login_times() {
2008 global $USER;
2009
53467aa6 2010 $user = new object();
1d881d92 2011 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
2a2f5f11 2012 $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
1d881d92 2013
2014 $user->id = $USER->id;
2015
b0ccd3fb 2016 return update_record('user', $user);
1d881d92 2017}
2018
7cf1c7bd 2019/**
2020 * Determines if a user has completed setting up their account.
2021 *
89dcb99d 2022 * @param user $user A {@link $USER} object to test for the existance of a valid name and email
bbd3f2c4 2023 * @return bool
7cf1c7bd 2024 */
808a3baa 2025function user_not_fully_set_up($user) {
bb64b51a 2026 return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
2027}
2028
2029function over_bounce_threshold($user) {
d2a9f7cc 2030
bb64b51a 2031 global $CFG;
d2a9f7cc 2032
bb64b51a 2033 if (empty($CFG->handlebounces)) {
2034 return false;
2035 }
2036 // set sensible defaults
2037 if (empty($CFG->minbounces)) {
2038 $CFG->minbounces = 10;
2039 }
2040 if (empty($CFG->bounceratio)) {
2041 $CFG->bounceratio = .20;
2042 }
2043 $bouncecount = 0;
2044 $sendcount = 0;
2045 if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2046 $bouncecount = $bounce->value;
2047 }
2048 if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2049 $sendcount = $send->value;
2050 }
2051 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
2052}
2053
d2a9f7cc 2054/**
bb64b51a 2055 * @param $user - object containing an id
2056 * @param $reset - will reset the count to 0
2057 */
2058function set_send_count($user,$reset=false) {
d2a9f7cc 2059 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
bb64b51a 2060 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2061 update_record('user_preferences',$pref);
2062 }
2063 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2064 // make a new one
2065 $pref->name = 'email_send_count';
2066 $pref->value = 1;
2067 $pref->userid = $user->id;
06ba0b04 2068 insert_record('user_preferences',$pref, false);
bb64b51a 2069 }
2070}
2071
d2a9f7cc 2072/**
bb64b51a 2073* @param $user - object containing an id
2074 * @param $reset - will reset the count to 0
2075 */
2076function set_bounce_count($user,$reset=false) {
d2a9f7cc 2077 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
bb64b51a 2078 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2079 update_record('user_preferences',$pref);
2080 }
2081 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2082 // make a new one
2083 $pref->name = 'email_bounce_count';
2084 $pref->value = 1;
2085 $pref->userid = $user->id;
06ba0b04 2086 insert_record('user_preferences',$pref, false);
bb64b51a 2087 }
808a3baa 2088}
f9903ed0 2089
7cf1c7bd 2090/**
2091 * Keeps track of login attempts
2092 *
2093 * @uses $SESSION
2094 */
f9903ed0 2095function update_login_count() {
9fa49e22 2096
f9903ed0 2097 global $SESSION;
2098
2099 $max_logins = 10;
2100
2101 if (empty($SESSION->logincount)) {
2102 $SESSION->logincount = 1;
2103 } else {
2104 $SESSION->logincount++;
2105 }
2106
2107 if ($SESSION->logincount > $max_logins) {
9fa49e22 2108 unset($SESSION->wantsurl);
b0ccd3fb 2109 error(get_string('errortoomanylogins'));
d578afc8 2110 }
2111}
2112
7cf1c7bd 2113/**
2114 * Resets login attempts
2115 *
2116 * @uses $SESSION
2117 */
9fa49e22 2118function reset_login_count() {
9fa49e22 2119 global $SESSION;
d578afc8 2120
9fa49e22 2121 $SESSION->logincount = 0;
d578afc8 2122}
2123
b61efafb 2124function sync_metacourses() {
2125
2126 global $CFG;
2127
1aad4310 2128 if (!$courses = get_records('course', 'metacourse', 1)) {
b61efafb 2129 return;
2130 }
d2a9f7cc 2131
b61efafb 2132 foreach ($courses as $course) {
1aad4310 2133 sync_metacourse($course);
b61efafb 2134 }
2135}
2136
b61efafb 2137/**
2138 * Goes through all enrolment records for the courses inside the metacourse and sync with them.
5e623a33 2139 *
123545bc 2140 * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
d2a9f7cc 2141 */
1aad4310 2142function sync_metacourse($course) {
755c8d58 2143 global $CFG;
b61efafb 2144
123545bc 2145 // Check the course is valid.
1aad4310 2146 if (!is_object($course)) {
2147 if (!$course = get_record('course', 'id', $course)) {
2148 return false; // invalid course id
b61efafb 2149 }
b61efafb 2150 }
5e623a33 2151
123545bc 2152 // Check that we actually have a metacourse.
1aad4310 2153 if (empty($course->metacourse)) {
123545bc 2154 return false;
755c8d58 2155 }
87671466 2156
b3170072 2157 // Get a list of roles that should not be synced.
4db9bff7 2158 if (!empty($CFG->nonmetacoursesyncroleids)) {
b3170072 2159 $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
5e623a33 2160 } else {
b3170072 2161 $roleexclusions = '';
2162 }
2163
123545bc 2164 // Get the context of the metacourse.
1aad4310 2165 $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
e1ecf0a0 2166
123545bc 2167 // We do not ever want to unassign the list of metacourse manager, so get a list of them.
b79da3ac 2168 if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
1aad4310 2169 $managers = array_keys($users);
2170 } else {
2171 $managers = array();
b61efafb 2172 }
2173
123545bc 2174 // Get assignments of a user to a role that exist in a child course, but
2175 // not in the meta coure. That is, get a list of the assignments that need to be made.
2176 if (!$assignments = get_records_sql("
2177 SELECT
2178 ra.id, ra.roleid, ra.userid
2179 FROM
2180 {$CFG->prefix}role_assignments ra,
2181 {$CFG->prefix}context con,
2182 {$CFG->prefix}course_meta cm
2183 WHERE
2184 ra.contextid = con.id AND
2185 con.contextlevel = " . CONTEXT_COURSE . " AND
2186 con.instanceid = cm.child_course AND
2187 cm.parent_course = {$course->id} AND
b3170072 2188 $roleexclusions
123545bc 2189 NOT EXISTS (
2190 SELECT 1 FROM
2191 {$CFG->prefix}role_assignments ra2
2192 WHERE
2193 ra2.userid = ra.userid AND
2194 ra2.roleid = ra.roleid AND
2195 ra2.contextid = {$context->id}
2196 )
2197 ")) {
2198 $assignments = array();
2199 }
2200
2201 // Get assignments of a user to a role that exist in the meta course, but
2202 // not in any child courses. That is, get a list of the unassignments that need to be made.
2203 if (!$unassignments = get_records_sql("
2204 SELECT
2205 ra.id, ra.roleid, ra.userid
2206 FROM
2207 {$CFG->prefix}role_assignments ra
2208 WHERE
2209 ra.contextid = {$context->id} AND
b3170072 2210 $roleexclusions
123545bc 2211 NOT EXISTS (
2212 SELECT 1 FROM
2213 {$CFG->prefix}role_assignments ra2,
2214 {$CFG->prefix}context con2,
2215 {$CFG->prefix}course_meta cm
2216 WHERE
2217 ra2.userid = ra.userid AND
2218 ra2.roleid = ra.roleid AND
2219 ra2.contextid = con2.id AND
2220 con2.contextlevel = " . CONTEXT_COURSE . " AND
2221 con2.instanceid = cm.child_course AND
2222 cm.parent_course = {$course->id}
2223 )
2224 ")) {
2225 $unassignments = array();
2226 }
2227
2228 $success = true;
2229
2230 // Make the unassignments, if they are not managers.
2231 foreach ($unassignments as $unassignment) {
2232 if (!in_array($unassignment->userid, $managers)) {
2233 $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
1aad4310 2234 }
755c8d58 2235 }
e1ecf0a0 2236
123545bc 2237 // Make the assignments.
2238 foreach ($assignments as $assignment) {
2239 $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id) && $success;
b61efafb 2240 }
755c8d58 2241
123545bc 2242 return $success;
5e623a33 2243
1aad4310 2244// TODO: finish timeend and timestart
2245// maybe we could rely on cron job to do the cleaning from time to time
b61efafb 2246}
2247
d2a9f7cc 2248/**
b61efafb 2249 * Adds a record to the metacourse table and calls sync_metacoures
2250 */
2251function add_to_metacourse ($metacourseid, $courseid) {
d2a9f7cc 2252
b61efafb 2253 if (!$metacourse = get_record("course","id",$metacourseid)) {
2254 return false;
2255 }
d2a9f7cc 2256
b61efafb 2257 if (!$course = get_record("course","id",$courseid)) {
2258 return false;
2259 }
2260
5f37b628 2261 if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
53467aa6 2262 $rec = new object();
b61efafb 2263 $rec->parent_course = $metacourseid;
2264 $rec->child_course = $courseid;
5f37b628 2265 if (!insert_record('course_meta',$rec)) {
b61efafb 2266 return false;
2267 }
2268 return sync_metacourse($metacourseid);
2269 }
2270 return true;
d2a9f7cc 2271
b61efafb 2272}
2273
d2a9f7cc 2274/**
b61efafb 2275 * Removes the record from the metacourse table and calls sync_metacourse
2276 */
2277function remove_from_metacourse($metacourseid, $courseid) {
2278
5f37b628 2279 if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
b61efafb 2280 return sync_metacourse($metacourseid);
2281 }
2282 return false;
2283}
2284
2285
7c12949d 2286/**
2287 * Determines if a user is currently logged in
2288 *
2289 * @uses $USER
bbd3f2c4 2290 * @return bool
7c12949d 2291 */
2292function isloggedin() {
2293 global $USER;
2294
2295 return (!empty($USER->id));
2296}
2297
2a919fd7 2298/**
2299 * Determines if a user is logged in as real guest user with username 'guest'.
2300 * This function is similar to original isguest() in 1.6 and earlier.
2301 * Current isguest() is deprecated - do not use it anymore.
2302 *
2303 * @param $user mixed user object or id, $USER if not specified
2304 * @return bool true if user is the real guest user, false if not logged in or other user
2305 */
2306function isguestuser($user=NULL) {
2307 global $USER;
2308 if ($user === NULL) {
2309 $user = $USER;
2310 } else if (is_numeric($user)) {
2311 $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2312 }
2313
2314 if (empty($user->id)) {
2315 return false; // not logged in, can not be guest
2316 }
2317
2318 return ($user->username == 'guest');
2319}
7c12949d 2320
7cf1c7bd 2321/**
e6260a45 2322 * Determines if the currently logged in user is in editing mode.
2323 * Note: originally this function had $userid parameter - it was not usable anyway
7cf1c7bd 2324 *
2325 * @uses $USER
c6d15803 2326 * @param int $courseid The id of the course being tested
bbd3f2c4 2327 * @return bool
7cf1c7bd 2328 */
e6260a45 2329function isediting($courseid) {
3cf4fa0c 2330 global $USER;
e6260a45 2331
2332 if (empty($USER->editing)) {
9c9f7d77 2333 return false;
e6260a45 2334
2335 } else {
2336 return editcourseallowed($courseid);
9c9f7d77 2337 }
e6260a45 2338}
2339
2340/**
2341 * Verifies if user allowed to edit something in the course page.
2342 * @param int $courseid The id of the course being tested
2343 * @return bool
2344 */
2345function editcourseallowed($courseid) {
2346 global $USER;
5e623a33 2347
e6260a45 2348 // cache the result per course, it is automatically reset when using switchrole or loginas
2349 if (!array_key_exists('courseeditallowed', $USER)) {
2350 $USER->courseeditallowed = array();
2351 }
5e623a33 2352
e6260a45 2353 if (!array_key_exists($courseid, $USER->courseeditallowed)) {
2354 $USER->courseeditallowed[$courseid] = has_capability_including_child_contexts(get_context_instance(CONTEXT_COURSE, $courseid),
2355 array('moodle/site:manageblocks', 'moodle/course:manageactivities'));
217a8ee9 2356 }
5e623a33 2357
e6260a45 2358 return $USER->courseeditallowed[$courseid];
2c309dc2 2359}
2360
7cf1c7bd 2361/**
2362 * Determines if the logged in user is currently moving an activity
2363 *
2364 * @uses $USER
c6d15803 2365 * @param int $courseid The id of the course being tested
bbd3f2c4 2366 * @return bool
7cf1c7bd 2367 */
7977cffd 2368function ismoving($courseid) {
7977cffd 2369 global $USER;
2370
2371 if (!empty($USER->activitycopy)) {
2372 return ($USER->activitycopycourse == $courseid);
2373 }
2374 return false;
2375}
2376
7cf1c7bd 2377/**
2378 * Given an object containing firstname and lastname
2379 * values, this function returns a string with the
2380 * full name of the person.
2381 * The result may depend on system settings
2382 * or language. 'override' will force both names
361855e6 2383 * to be used even if system settings specify one.
68fbd8e1 2384 *
7cf1c7bd 2385 * @uses $CFG
2386 * @uses $SESSION
68fbd8e1 2387 * @param object $user A {@link $USER} object to get full name of
2388 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
7cf1c7bd 2389 */
e2cd5065 2390function fullname($user, $override=false) {
b5cbb64d 2391
f374fb10 2392 global $CFG, $SESSION;
2393
6527c077 2394 if (!isset($user->firstname) and !isset($user->lastname)) {
2395 return '';
2396 }
2397
4c202228 2398 if (!$override) {
2399 if (!empty($CFG->forcefirstname)) {
2400 $user->firstname = $CFG->forcefirstname;
2401 }
2402 if (!empty($CFG->forcelastname)) {
2403 $user->lastname = $CFG->forcelastname;
2404 }
2405 }
2406
f374fb10 2407 if (!empty($SESSION->fullnamedisplay)) {
2408 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2409 }
e2cd5065 2410
b5cbb64d 2411 if ($CFG->fullnamedisplay == 'firstname lastname') {
b0ccd3fb 2412 return $user->firstname .' '. $user->lastname;
b5cbb64d 2413
2414 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
b0ccd3fb 2415 return $user->lastname .' '. $user->firstname;
e2cd5065 2416
b5cbb64d 2417 } else if ($CFG->fullnamedisplay == 'firstname') {
2418 if ($override) {
2419 return get_string('fullnamedisplay', '', $user);
2420 } else {
2421 return $user->firstname;
2422 }
2423 }
e2cd5065 2424
b5cbb64d 2425 return get_string('fullnamedisplay', '', $user);
e2cd5065 2426}
2427
7cf1c7bd 2428/**
2429 * Sets a moodle cookie with an encrypted string
2430 *
2431 * @uses $CFG
2f87145b 2432 * @uses DAYSECS
2433 * @uses HOURSECS
7cf1c7bd 2434 * @param string $thing The string to encrypt and place in a cookie
2435 */
f9903ed0 2436function set_moodle_cookie($thing) {
7185e073 2437 global $CFG;
482b6e6e 2438
7cbe6afe 2439 if ($thing == 'guest') { // Ignore guest account
2440 return;
2441 }
2442
482b6e6e 2443 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
f9903ed0 2444
2445 $days = 60;
7a5672c9 2446 $seconds = DAYSECS*$days;
f9903ed0 2447
7a5672c9 2448 setCookie($cookiename, '', time() - HOURSECS, '/');
b0ccd3fb 2449 setCookie($cookiename, rc4encrypt($thing), time()+$seconds, '/');
f9903ed0 2450}
2451
7cf1c7bd 2452/**
2453 * Gets a moodle cookie with an encrypted string
2454 *
2455 * @uses $CFG
2456 * @return string
2457 */
f9903ed0 2458function get_moodle_cookie() {
7185e073 2459 global $CFG;
2460
482b6e6e 2461 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
7185e073 2462
1079c8a8 2463 if (empty($_COOKIE[$cookiename])) {
b0ccd3fb 2464 return '';
1079c8a8 2465 } else {
7cbe6afe 2466 $thing = rc4decrypt($_COOKIE[$cookiename]);
2467 return ($thing == 'guest') ? '': $thing; // Ignore guest account
1079c8a8 2468 }
f9903ed0 2469}
2470
7cf1c7bd 2471/**
03d820c7 2472 * Returns whether a given authentication plugin exists.
7cf1c7bd 2473 *
2474 * @uses $CFG
03d820c7 2475 * @param string $auth Form of authentication to check for. Defaults to the
2476 * global setting in {@link $CFG}.
2477 * @return boolean Whether the plugin is available.
7cf1c7bd 2478 */
16793340 2479function exists_auth_plugin($auth) {
03d820c7 2480 global $CFG;
5e623a33 2481
03d820c7 2482 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2483 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2484 }
2485 return false;
2486}
ba7166c3 2487
03d820c7 2488/**
2489 * Checks if a given plugin is in the list of enabled authentication plugins.
5e623a33 2490 *
03d820c7 2491 * @param string $auth Authentication plugin.
2492 * @return boolean Whether the plugin is enabled.
2493 */
16793340 2494function is_enabled_auth($auth) {
16793340 2495 if (empty($auth)) {
2496 return false;
03d820c7 2497 }
16793340 2498
c7b10b5f 2499 $enabled = get_enabled_auth_plugins();
2500
2501 return in_array($auth, $enabled);
03d820c7 2502}
2503
2504/**
2505 * Returns an authentication plugin instance.
2506 *
2507 * @uses $CFG
9696bd89 2508 * @param string $auth name of authentication plugin
03d820c7 2509 * @return object An instance of the required authentication plugin.
2510 */
9696bd89 2511function get_auth_plugin($auth) {
03d820c7 2512 global $CFG;
5e623a33 2513
03d820c7 2514 // check the plugin exists first
2515 if (! exists_auth_plugin($auth)) {
2516 error("Authentication plugin '$auth' not found.");
2517 }
5e623a33 2518
03d820c7 2519 // return auth plugin instance
2520 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2521 $class = "auth_plugin_$auth";
2522 return new $class;
2523}
2524
c7b10b5f 2525/**
2526 * Returns array of active auth plugins.
2527 *
2528 * @param bool $fix fix $CFG->auth if needed
2529 * @return array
2530 */
2531function get_enabled_auth_plugins($fix=false) {
2532 global $CFG;
2533
2534 $default = array('manual', 'nologin');
2535
2536 if (empty($CFG->auth)) {
2537 $auths = array();
2538 } else {
2539 $auths = explode(',', $CFG->auth);
2540 }
2541
2542 if ($fix) {
2543 $auths = array_unique($auths);
2544 foreach($auths as $k=>$authname) {
2545 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
2546 unset($auths[$k]);
2547 }
2548 }
2549 $newconfig = implode(',', $auths);
2550 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
2551 set_config('auth', $newconfig);
2552 }
2553 }
2554
2555 return (array_merge($default, $auths));
2556}
2557
03d820c7 2558/**
2559 * Returns true if an internal authentication method is being used.
2560 * if method not specified then, global default is assumed
2561 *
2562 * @uses $CFG
2563 * @param string $auth Form of authentication required
2564 * @return bool
03d820c7 2565 */
16793340 2566function is_internal_auth($auth) {
03d820c7 2567 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2568 return $authplugin->is_internal();
a3f1f815 2569}
2570
8c3dba73 2571/**
2572 * Returns an array of user fields
2573 *
c6d15803 2574 * @uses $CFG
2575 * @uses $db
2576 * @return array User field/column names
8c3dba73 2577 */
a3f1f815 2578function get_user_fieldnames() {
a3f1f815 2579
2580 global $CFG, $db;
2581
2582 $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2583 unset($fieldarray['ID']);
2584
2585 return $fieldarray;
ba7166c3 2586}
f9903ed0 2587
08103c93
ML
2588/**
2589 * Creates the default "guest" user. Used both from
2590 * admin/index.php and login/index.php
2591 * @return mixed user object created or boolean false if the creation has failed
2592 */
2593function create_guest_record() {
2594
2595 global $CFG;
2596
c824d478 2597 $guest = new stdClass();
08103c93
ML
2598 $guest->auth = 'manual';
2599 $guest->username = 'guest';
2600 $guest->password = hash_internal_user_password('guest');
2601 $guest->firstname = addslashes(get_string('guestuser'));
2602 $guest->lastname = ' ';
2603 $guest->email = 'root@localhost';
2604 $guest->description = addslashes(get_string('guestuserinfo'));
2605 $guest->mnethostid = $CFG->mnet_localhost_id;
2606 $guest->confirmed = 1;
2607 $guest->lang = $CFG->lang;
2608 $guest->timemodified= time();
2609
2610 if (! $guest->id = insert_record("user", $guest)) {
2611 return false;
2612 }
2613
2614 return $guest;
2615}
2616
7cf1c7bd 2617/**
2618 * Creates a bare-bones user record
2619 *
2620 * @uses $CFG
7cf1c7bd 2621 * @param string $username New user's username to add to record
2622 * @param string $password New user's password to add to record
2623 * @param string $auth Form of authentication required
68fbd8e1 2624 * @return object A {@link $USER} object
7cf1c7bd 2625 * @todo Outline auth types and provide code example
2626 */
f76cfc7a 2627function create_user_record($username, $password, $auth='manual') {
366dfa60 2628 global $CFG;
71f9abf9 2629
1e22bc9c 2630 //just in case check text case
2631 $username = trim(moodle_strtolower($username));
71f9abf9 2632
03d820c7 2633 $authplugin = get_auth_plugin($auth);
2634
6bc1e5d5 2635 if ($newinfo = $authplugin->get_userinfo($username)) {
2636 $newinfo = truncate_userinfo($newinfo);
2637 foreach ($newinfo as $key => $value){
2638 $newuser->$key = addslashes($value);
e858f9da 2639 }
2640 }
f9903ed0 2641
85a1d4c9 2642 if (!empty($newuser->email)) {
2643 if (email_is_not_allowed($newuser->email)) {
2644 unset($newuser->email);
2645 }
2646 }
2647
f76cfc7a 2648 $newuser->auth = $auth;
faebaf0f 2649 $newuser->username = $username;
5e623a33 2650
e51917eb 2651 // fix for MDL-8480
2652 // user CFG lang for user if $newuser->lang is empty
2653 // or $user->lang is not an installed language
2654 $sitelangs = array_keys(get_list_of_languages());
2655 if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
2656 $newuser -> lang = $CFG->lang;
5e623a33 2657 }
faebaf0f 2658 $newuser->confirmed = 1;
d96466d2 2659 $newuser->lastip = getremoteaddr();
faebaf0f 2660 $newuser->timemodified = time();
03d820c7 2661 $newuser->mnethostid = $CFG->mnet_localhost_id;
f9903ed0 2662
b0ccd3fb 2663 if (insert_record('user', $newuser)) {
16793340 2664 $user = get_complete_user_data('username', $newuser->username);
da5bcc9f 2665 if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
16793340 2666 set_user_preference('auth_forcepasswordchange', 1, $user->id);
2667 }
2668 update_internal_user_password($user, $password);
2669 return $user;
faebaf0f 2670 }
2671 return false;
2672}
2673
7cf1c7bd 2674/**
2675 * Will update a local user record from an external source
2676 *
2677 * @uses $CFG
2678 * @param string $username New user's username to add to record
89dcb99d 2679 * @return user A {@link $USER} object
7cf1c7bd 2680 */
03d820c7 2681function update_user_record($username, $authplugin) {
6bc1e5d5 2682 $username = trim(moodle_strtolower($username)); /// just in case check text case
2683
2684 $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2685 $userauth = get_auth_plugin($oldinfo->auth);
2686
2687 if ($newinfo = $userauth->get_userinfo($username)) {
2688 $newinfo = truncate_userinfo($newinfo);
2689 foreach ($newinfo as $key => $value){
2690 $confkey = 'field_updatelocal_' . $key;
2691 if (!empty($userauth->config->$confkey) and $userauth->config->$confkey === 'onlogin') {
2692 $value = addslashes(stripslashes($value)); // Just in case
2693 set_field('user', $key, $value, 'username', $username)
2694 or error_log("Error updating $key for $username");
d35757eb 2695 }
2696 }
2697 }
6bc1e5d5 2698
7c12949d 2699 return get_complete_user_data('username', $username);
d35757eb 2700}
0609562b 2701
b36a8fc4 2702function truncate_userinfo($info) {
2703/// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2704/// which may have large fields
2705
2706 // define the limits
2707 $limit = array(
2708 'username' => 100,
1c66bf59 2709 'idnumber' => 64,
8bcd295c 2710 'firstname' => 100,
2711 'lastname' => 100,
b36a8fc4 2712 'email' => 100,
2713 'icq' => 15,
2714 'phone1' => 20,
2715 'phone2' => 20,
2716 'institution' => 40,
2717 'department' => 30,
2718 'address' => 70,
2719 'city' => 20,
2720 'country' => 2,
2721 'url' => 255,
2722 );
361855e6 2723
b36a8fc4 2724 // apply where needed
2725 foreach (array_keys($info) as $key) {
2726 if (!empty($limit[$key])) {
adfc03f9 2727 $info[$key] = trim(substr($info[$key],0, $limit[$key]));
361855e6 2728 }
b36a8fc4 2729 }
361855e6 2730
b36a8fc4 2731 return $info;
90afcf32 2732}
2733
2734/**
2735 * Marks user deleted in internal user database and notifies the auth plugin.
2736 * Also unenrols user from all roles and does other cleanup.
2737 * @param object $user Userobject before delete (without system magic quotes)
2738 * @return boolean success
2739 */
2740function delete_user($user) {
2741 global $CFG;
2742 require_once($CFG->libdir.'/grouplib.php');
2743
2744 begin_sql();
2745
2746 // delete all grades - backup is kept in grade_grades_history table
2747 if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
2748 foreach ($grades as $grade) {
2749 $grade->delete('userdelete');
2750 }
2751 }
2752
2753 // remove from all groups
2754 delete_records('groups_members', 'userid', $user->id);
2755
2756 // unenrol from all roles in all contexts
2757 role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
2758
2759 // now do a final accesslib cleanup - removes all role assingments in user context and context itself
2760 delete_context(CONTEXT_USER, $user->id);
2761
2762 // mark internal user record as "deleted"
2763 $updateuser = new object();
2764 $updateuser->id = $user->id;
2765 $updateuser->deleted = 1;
2766 $updateuser->username = addslashes("$user->email.".time()); // Remember it just in case
2767 $updateuser->email = ''; // Clear this field to free it up
2768 $updateuser->idnumber = ''; // Clear this field to free it up
2769 $updateuser->timemodified = time();
2770
2771 if (update_record('user', $updateuser)) {
2772 commit_sql();
2773 // notify auth plugin - do not block the delete even when plugin fails
2774 $authplugin = get_auth_plugin($user->auth);
2775 $authplugin->user_delete($user);
2776 return true;
2777
2778 } else {
2779 rollback_sql();
2780 return false;
2781 }
b36a8fc4 2782}
2783
7cf1c7bd 2784/**
2785 * Retrieve the guest user object
2786 *
2787 * @uses $CFG
89dcb99d 2788 * @return user A {@link $USER} object
7cf1c7bd 2789 */
0609562b 2790function guest_user() {
2791 global $CFG;
2792
b59c7ec0 2793 if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id)) {
0609562b 2794 $newuser->confirmed = 1;
0609562b 2795 $newuser->lang = $CFG->lang;
d96466d2 2796 $newuser->lastip = getremoteaddr();
0609562b 2797 }
2798
2799 return $newuser;
2800}
2801
7cf1c7bd 2802/**
2803 * Given a username and password, this function looks them
2804 * up using the currently selected authentication mechanism,
2805 * and if the authentication is successful, it returns a
2806 * valid $user object from the 'user' table.
361855e6 2807 *
7cf1c7bd 2808 * Uses auth_ functions from the currently active auth module
2809 *
2810 * @uses $CFG
f5fd4347 2811 * @param string $username User's username (with system magic quotes)
2812 * @param string $password User's password (with system magic quotes)
89dcb99d 2813 * @return user|flase A {@link $USER} object or false if error
7cf1c7bd 2814 */
faebaf0f 2815function authenticate_user_login($username, $password) {
faebaf0f 2816
2817 global $CFG;
2818
c7b10b5f 2819 $authsenabled = get_enabled_auth_plugins();
39a5a35d 2820
16793340 2821 if ($user = get_complete_user_data('username', $username)) {
2822 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
2823 if ($auth=='nologin' or !is_enabled_auth($auth)) {
2824 add_to_log(0, 'login', 'error', 'index.php', $username);
2825 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
2826 return false;
27286aeb 2827 }
16793340 2828 if (!empty($user->deleted)) {
2829 add_to_log(0, 'login', 'error', 'index.php', $username);
2830 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
2831 return false;
d35757eb 2832 }
16793340 2833 $auths = array($auth);
2834
71f9abf9 2835 } else {
16793340 2836 $auths = $authsenabled;
2837 $user = new object();
2838 $user->id = 0; // User does not exist
27286aeb 2839 }
8f0cd6ef 2840
03d820c7 2841 foreach ($auths as $auth) {
2842 $authplugin = get_auth_plugin($auth);
466558e3 2843
16793340 2844 // on auth fail fall through to the next plugin
03d820c7 2845 if (!$authplugin->user_login($username, $password)) {
03d820c7 2846 continue;
2847 }
faebaf0f 2848
03d820c7 2849 // successful authentication
d613daf0 2850 if ($user->id) { // User already exists in database
71f9abf9 2851 if (empty($user->auth)) { // For some reason auth isn't set yet
2852 set_field('user', 'auth', $auth, 'username', $username);
16793340 2853 $user->auth = $auth;
71f9abf9 2854 }
16793340 2855
2856 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
2857
03d820c7 2858 if (!$authplugin->is_internal()) { // update user record from external DB
2859 $user = update_user_record($username, get_auth_plugin($user->auth));
d35757eb 2860 }
faebaf0f 2861 } else {
16793340 2862 // if user not found, create him
71f9abf9 2863 $user = create_user_record($username, $password, $auth);
faebaf0f 2864 }
01af6da6 2865
6bc1e5d5 2866 $authplugin->sync_roles($user);
2867
f5fd4347 2868 foreach ($authsenabled as $hau) {
2869 $hauth = get_auth_plugin($hau);
2870 $hauth->user_authenticated_hook($user, $username, $password);
2871 }
2872
2873 /// Log in to a second system if necessary
2874 /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
2875 if (!empty($CFG->sso)) {
2876 include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
2877 if (function_exists('sso_user_login')) {
2878 if (!sso_user_login($username, $password)) { // Perform the signon process
2879 notify('Second sign-on failed');
2880 }
2881 }
2882 }
01af6da6 2883
e582b65e 2884 return $user;
9d3c795c 2885
5e623a33 2886 }
2887
03d820c7 2888 // failed if all the plugins have failed
2889 add_to_log(0, 'login', 'error', 'index.php', $username);
2890 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
2891 return false;
f9903ed0 2892}
2893
df193157 2894/**
4908ad3e 2895 * Compare password against hash stored in internal user table.
df193157 2896 * If necessary it also updates the stored hash to new format.
5e623a33 2897 *
df193157 2898 * @param object user
2899 * @param string plain text password
2900 * @return bool is password valid?
2901 */
2902function validate_internal_user_password(&$user, $password) {
2903 global $CFG;
2904
4908ad3e 2905 if (!isset($CFG->passwordsaltmain)) {
2906 $CFG->passwordsaltmain = '';
2907 }
2908
df193157 2909 $validated = false;
2910
a044c05d 2911 // get password original encoding in case it was not updated to unicode yet
fb773106 2912 $textlib = textlib_get_instance();
810944af 2913 $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
df193157 2914
4908ad3e 2915 if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
2916 or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
df193157 2917 $validated = true;
4908ad3e 2918 } else {
aaeaa4b0 2919 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
4908ad3e 2920 $alt = 'passwordsaltalt'.$i;
2921 if (!empty($CFG->$alt)) {
2922 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
2923 $validated = true;
2924 break;
2925 }
2926 }
2927 }
df193157 2928 }
2929
2930 if ($validated) {
4908ad3e 2931 // force update of password hash using latest main password salt and encoding if needed
df193157 2932 update_internal_user_password($user, $password);
2933 }
2934
2935 return $validated;
2936}
2937
2938/**
2939 * Calculate hashed value from password using current hash mechanism.
5e623a33 2940 *
df193157 2941 * @param string password
2942 * @return string password hash
2943 */
2944function hash_internal_user_password($password) {
4908ad3e 2945 global $CFG;
2946
2947 if (isset($CFG->passwordsaltmain)) {
2948 return md5($password.$CFG->passwordsaltmain);
2949 } else {
2950 return md5($password);
2951 }
df193157 2952}
2953
2954/**
2955 * Update pssword hash in user object.
5e623a33 2956 *
df193157 2957 * @param object user
2958 * @param string plain text password
2959 * @param bool store changes also in db, default true
2960 * @return true if hash changed
2961 */
16793340 2962function update_internal_user_password(&$user, $password) {
df193157 2963 global $CFG;
2964
03d820c7 2965 $authplugin = get_auth_plugin($user->auth);
16793340 2966 if (!empty($authplugin->config->preventpassindb)) {
df193157 2967 $hashedpassword = 'not cached';
2968 } else {
2969 $hashedpassword = hash_internal_user_password($password);
2970 }
2971
b7b50143 2972 return set_field('user', 'password', $hashedpassword, 'id', $user->id);
df193157 2973}
2974
7c12949d 2975/**
2976 * Get a complete user record, which includes all the info
5c98bf9e 2977 * in the user record
7c12949d 2978 * Intended for setting as $USER session variable
2979 *
2980 * @uses $CFG
2981 * @uses SITEID
e1ecf0a0 2982 * @param string $field The user field to be checked for a given value.
7c12949d 2983 * @param string $value The value to match for $field.
2984 * @return user A {@link $USER} object.
2985 */
b7b50143 2986function get_complete_user_data($field, $value, $mnethostid=null) {
7c12949d 2987
2988 global $CFG;
2989
2990 if (!$field || !$value) {
2991 return false;
2992 }
2993
b7b50143 2994/// Build the WHERE clause for an SQL query
2995
2996 $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
2997
e5edab1b 2998 if (is_null($mnethostid)) {
2999 // if null, we restrict to local users
3000 // ** testing for local user can be done with
5e623a33 3001 // mnethostid = $CFG->mnet_localhost_id
e5edab1b 3002 // or with
5e623a33 3003 // auth != 'mnet'
e5edab1b 3004 // but the first one is FAST with our indexes
3005 $mnethostid = $CFG->mnet_localhost_id;
3006 }
3007 $mnethostid = (int)$mnethostid;
3008 $constraints .= ' AND mnethostid = \''.$mnethostid.'\'';
b7b50143 3009
7c12949d 3010/// Get all the basic user data
3011
b7b50143 3012 if (! $user = get_record_select('user', $constraints)) {
7c12949d 3013 return false;
3014 }
3015
7c12949d 3016/// Get various settings and preferences
3017
3018 if ($displays = get_records('course_display', 'userid', $user->id)) {
3019 foreach ($displays as $display) {
3020 $user->display[$display->course] = $display->display;
3021 }
3022 }
3023
346c3e2f 3024 $user->preference = get_user_preferences(null, null, $user->id);
7c12949d 3025
721d14cb 3026 if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
3027 foreach ($lastaccesses as $lastaccess) {
3028 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
3029 }
3030 }
3031
5bf243d1 3032 $sql = "SELECT g.id, g.courseid
3033 FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
3034 WHERE gm.groupid=g.id AND gm.userid={$user->id}";
3035
3036 // this is a special hack to speedup calendar display
3037 $user->groupmember = array();
3038 if ($groups = get_records_sql($sql)) {
3039 foreach ($groups as $group) {
3040 if (!array_key_exists($group->courseid, $user->groupmember)) {
3041 $user->groupmember[$group->courseid] = array();
3042 }
3043 $user->groupmember[$group->courseid][$group->id] = $group->id;
7c12949d 3044 }
3045 }
3046
3047/// Rewrite some variables if necessary
3048 if (!empty($user->description)) {
3049 $user->description = true; // No need to cart all of it around
3050 }
3051 if ($user->username == 'guest') {
3052 $user->lang = $CFG->lang; // Guest language always same as site
3053 $user->firstname = get_string('guestuser'); // Name always in current language
3054 $user->lastname = ' ';
3055 }
3056
7c12949d 3057 $user->sesskey = random_string(10);
3058 $user->sessionIP = md5(getremoteaddr()); // Store the current IP in the session
3059
3060 return $user;
7c12949d 3061}
3062
83022298 3063/**
3064 * @uses $CFG
3065 * @param string $password the password to be checked agains the password policy
3066 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3067 * @return bool true if the password is valid according to the policy. false otherwise.
3068 */
3069function check_password_policy($password, &$errmsg) {
3070 global $CFG;
3071
3072 if (empty($CFG->passwordpolicy)) {
3073 return true;
3074 }
3075
3076 $textlib = new textlib();
3077 $errmsg = '';
3078 if ($textlib->strlen($password) < $CFG->minpasswordlength) {
3079 $errmsg = get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength);
3080
3081 } else if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
3082 $errmsg = get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits);
3083
3084 } else if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
3085 $errmsg = get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower);
3086
3087 } else if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
3088 $errmsg = get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper);
7c12949d 3089
83022298 3090 } else if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
3091 $errmsg = get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum);
5c98bf9e 3092
83022298 3093 } else if ($password == 'admin' or $password == 'password') {
3094 $errmsg = get_string('unsafepassword');
3095 }
3096
3097 if ($errmsg == '') {
3098 return true;
3099 } else {
3100 return false;
3101 }
3102}
3103
3104
3105/**
7c12949d 3106 * When logging in, this function is run to set certain preferences
3107 * for the current SESSION
3108 */
3109function set_login_session_preferences() {
7c7ca1b5 3110 global $SESSION, $CFG;
7c12949d 3111
3112 $SESSION->justloggedin = true;
3113
3114 unset($SESSION->lang);
7c12949d 3115
3116 // Restore the calendar filters, if saved
3117 if (intval(get_user_preferences('calendar_persistflt', 0))) {
3118 include_once($CFG->dirroot.'/calendar/lib.php');
a266c904 3119 calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
7c12949d 3120 }
3121}
3122
3123
b97c4164 3124/**
3125 * Delete a course, including all related data from the database,
3126 * and any associated files from the moodledata folder.
e1ecf0a0 3127 *
b97c4164 3128 * @param int $courseid The id of the course to delete.
3129 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3130 * @return bool true if all the removals succeeded. false if there were any failures. If this
3131 * method returns false, some of the removals will probably have succeeded, and others
3132 * failed, but you have no way of knowing which.
3133 */
3134function delete_course($courseid, $showfeedback = true) {
3135 global $CFG;
f615fbab 3136 require_once($CFG->libdir.'/gradelib.php');
b97c4164 3137 $result = true;
e1ecf0a0 3138
b97c4164 3139 if (!remove_course_contents($courseid, $showfeedback)) {
3140 if ($showfeedback) {
3141 notify("An error occurred while deleting some of the course contents.");
3142 }
3143 $result = false;
3144 }
3145
f615fbab 3146 remove_course_grades($courseid, $showfeedback);
3147
b97c4164 3148 if (!delete_records("course", "id", $courseid)) {
3149 if ($showfeedback) {
3150 notify("An error occurred while deleting the main course record.");
3151 }
3152 $result = false;
3153 }
3154
9991d157 3155 if (!delete_records('context', 'contextlevel', CONTEXT_COURSE, 'instanceid', $courseid)) {
eaa79489 3156 if ($showfeedback) {
3157 notify("An error occurred while deleting the main context record.");
3158 }
3159 $result = false;
3160 }
3161
b97c4164 3162 if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3163 if ($showfeedback) {
3164 notify("An error occurred while deleting the course files.");
3165 }
3166 $result = false;
3167 }
e1ecf0a0 3168
b97c4164 3169 return $result;
3170}
3171
7cf1c7bd 3172/**
3173 * Clear a course out completely, deleting all content
3174 * but don't delete the course itself
3175 *
7cf1c7bd 3176 * @uses $CFG
1f8ede91 3177 * @param int $courseid The id of the course that is being deleted
3178 * @param bool $showfeedback Whether to display notifications of each action the function performs.
b97c4164 3179 * @return bool true if all the removals succeeded. false if there were any failures. If this
3180 * method returns false, some of the removals will probably have succeeded, and others
3181 * failed, but you have no way of knowing which.
7cf1c7bd 3182 */
07aeb7b0 3183function remove_course_contents($courseid, $showfeedback=true) {
07aeb7b0 3184
1f8ede91 3185 global $CFG;
681666ab 3186 include_once($CFG->libdir.'/questionlib.php');
07aeb7b0 3187
3188 $result = true;
3189
b0ccd3fb 3190 if (! $course = get_record('course', 'id', $courseid)) {
3191 error('Course ID was incorrect (can\'t find it)');
07aeb7b0 3192 }
3193
b0ccd3fb 3194 $strdeleted = get_string('deleted');
07aeb7b0 3195
9991d157 3196/// First delete every instance of every module
d8ba183c 3197
b0ccd3fb 3198 if ($allmods = get_records('modules') ) {
07aeb7b0 3199 foreach ($allmods as $mod) {
3200 $modname = $mod->name;
b0ccd3fb 3201 $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3202 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
3203 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
07aeb7b0 3204 $count=0;
3205 if (file_exists($modfile)) {
3206 include_once($modfile);
3207 if (function_exists($moddelete)) {
b0ccd3fb 3208 if ($instances = get_records($modname, 'course', $course->id)) {
07aeb7b0 3209 foreach ($instances as $instance) {
eaa79489 3210 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
3bee1ead 3211 /// Delete activity context questions and question categories
3212 question_delete_activity($cm, $showfeedback);
9991d157 3213 delete_context(CONTEXT_MODULE, $cm->id);
eaa79489 3214 }
07aeb7b0 3215 if ($moddelete($instance->id)) {
3216 $count++;
eaa79489 3217
07aeb7b0 3218 } else {
7eec3390 3219 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
07aeb7b0 3220 $result = false;
3221 }
3222 }
3223 }
3224 } else {
b0ccd3fb 3225 notify('Function '. $moddelete() .'doesn\'t exist!');
07aeb7b0 3226 $result = false;
3227 }
3228
ca952b03 3229 if (function_exists($moddeletecourse)) {
f67172b6 3230 $moddeletecourse($course, $showfeedback);
ca952b03 3231 }
07aeb7b0 3232 }
3233 if ($showfeedback) {
b0ccd3fb 3234 notify($strdeleted .' '. $count .' x '. $modname);
07aeb7b0 3235 }
3236 }
3237 } else {
b0ccd3fb 3238 error('No modules are installed!');
07aeb7b0 3239 }
3240
9991d157 3241/// Give local code a chance to delete its references to this course.
c0b5b31a 3242 require_once('locallib.php');
3243 notify_local_delete_course($courseid, $showfeedback);
3244
9991d157 3245/// Delete course blocks
7f869120 3246
5e623a33 3247 if ($blocks = get_records_sql("SELECT *
7f869120 3248 FROM {$CFG->prefix}block_instance
3249 WHERE pagetype = '".PAGE_COURSE_VIEW."'
3250 AND pageid = $course->id")) {
eaa79489 3251 if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3252 if ($showfeedback) {
3253 notify($strdeleted .' block_instance');
3254 }
5e623a33 3255
f38dfd49 3256 require_once($CFG->libdir.'/blocklib.php');
9991d157 3257 foreach ($blocks as $block) { /// Delete any associated contexts for this block
5e623a33 3258
f38dfd49 3259 // Block instances are rarely created. Since the block instance is gone from the above delete
3260 // statement, calling delete_context() will generate a warning as get_context_instance could
5e623a33 3261 // no longer create the context as the block is already gone.
f38dfd49 3262 if (record_exists('context', 'contextlevel', CONTEXT_BLOCK, 'instanceid', $block->id)) {
3263 delete_context(CONTEXT_BLOCK, $block->id);
3264 }
5e623a33 3265
3266 // fix for MDL-7164
f38dfd49 3267 // Get the block object and call instance_delete()
3268 if (!$record = blocks_get_record($block->blockid)) {
3269 $result = false;
3270 continue;
3271 }
3272 if (!$obj = block_instance($record->name, $block)) {
3273 $result = false;
3274 continue;