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