Project

General

Profile

moodlelib.php

David Ragot, 11/18/2010 10:48 AM

Download (279 KB)

 
1
<?php // $Id$
2

    
3
///////////////////////////////////////////////////////////////////////////
4
//                                                                       //
5
// NOTICE OF COPYRIGHT                                                   //
6
//                                                                       //
7
// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
8
//          http://moodle.org                                            //
9
//                                                                       //
10
// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
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
///////////////////////////////////////////////////////////////////////////
25

    
26
/**
27
 * moodlelib.php - Moodle main library
28
 *
29
 * Main library file of miscellaneous general-purpose Moodle functions.
30
 * Other main libraries:
31
 *  - weblib.php      - functions that produce web output
32
 *  - datalib.php     - functions that access the database
33
 * @author Martin Dougiamas
34
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
35
 * @package moodlecore
36
 */
37

    
38
/// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
39

    
40
/**
41
 * Used by some scripts to check they are being called by Moodle
42
 */
43
define('MOODLE_INTERNAL', true);
44

    
45
/// Date and time constants ///
46
/**
47
 * Time constant - the number of seconds in a year
48
 */
49

    
50
define('YEARSECS', 31536000);
51

    
52
/**
53
 * Time constant - the number of seconds in a week
54
 */
55
define('WEEKSECS', 604800);
56

    
57
/**
58
 * Time constant - the number of seconds in a day
59
 */
60
define('DAYSECS', 86400);
61

    
62
/**
63
 * Time constant - the number of seconds in an hour
64
 */
65
define('HOURSECS', 3600);
66

    
67
/**
68
 * Time constant - the number of seconds in a minute
69
 */
70
define('MINSECS', 60);
71

    
72
/**
73
 * Time constant - the number of minutes in a day
74
 */
75
define('DAYMINS', 1440);
76

    
77
/**
78
 * Time constant - the number of minutes in an hour
79
 */
80
define('HOURMINS', 60);
81

    
82
/// Parameter constants - every call to optional_param(), required_param()  ///
83
/// or clean_param() should have a specified type of parameter.  //////////////
84

    
85
/**
86
 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
87
 * originally was 0, but changed because we need to detect unknown
88
 * parameter types and swiched order in clean_param().
89
 */
90
define('PARAM_RAW', 666);
91

    
92
/**
93
 * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
94
 * It was one of the first types, that is why it is abused so much ;-)
95
 */
96
define('PARAM_CLEAN',    0x0001);
97

    
98
/**
99
 * PARAM_INT - integers only, use when expecting only numbers.
100
 */
101
define('PARAM_INT',      0x0002);
102

    
103
/**
104
 * PARAM_INTEGER - an alias for PARAM_INT
105
 */
106
define('PARAM_INTEGER',  0x0002);
107

    
108
/**
109
 * PARAM_NUMBER - a real/floating point number.
110
 */
111
define('PARAM_NUMBER',  0x000a);
112

    
113
/**
114
 * PARAM_ALPHA - contains only english letters.
115
 */
116
define('PARAM_ALPHA',    0x0004);
117

    
118
/**
119
 * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
120
 * @TODO: should we alias it to PARAM_ALPHANUM ?
121
 */
122
define('PARAM_ACTION',   0x0004);
123

    
124
/**
125
 * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
126
 * @TODO: should we alias it to PARAM_ALPHANUM ?
127
 */
128
define('PARAM_FORMAT',   0x0004);
129

    
130
/**
131
 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
132
 */
133
define('PARAM_NOTAGS',   0x0008);
134

    
135
 /**
136
 * PARAM_MULTILANG - alias of PARAM_TEXT.
137
 */
138
define('PARAM_MULTILANG',  0x0009);
139

    
140
 /**
141
 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
142
 */
143
define('PARAM_TEXT',  0x0009);
144

    
145
/**
146
 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
147
 */
148
define('PARAM_FILE',     0x0010);
149

    
150
/**
151
 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
152
 */
153
define('PARAM_TAG',   0x0011);
154

    
155
/**
156
 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
157
 */
158
define('PARAM_TAGLIST',   0x0012);
159

    
160
/**
161
 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
162
 * note: the leading slash is not removed, window drive letter is not allowed
163
 */
164
define('PARAM_PATH',     0x0020);
165

    
166
/**
167
 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
168
 */
169
define('PARAM_HOST',     0x0040);
170

    
171
/**
172
 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
173
 */
174
define('PARAM_URL',      0x0080);
175

    
176
/**
177
 * 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!)
178
 */
179
define('PARAM_LOCALURL', 0x0180);
180

    
181
/**
182
 * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
183
 * use when you want to store a new file submitted by students
184
 */
185
define('PARAM_CLEANFILE',0x0200);
186

    
187
/**
188
 * PARAM_ALPHANUM - expected numbers and letters only.
189
 */
190
define('PARAM_ALPHANUM', 0x0400);
191

    
192
/**
193
 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
194
 */
195
define('PARAM_BOOL',     0x0800);
196

    
197
/**
198
 * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
199
 * note: do not forget to addslashes() before storing into database!
200
 */
201
define('PARAM_CLEANHTML',0x1000);
202

    
203
/**
204
 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
205
 * suitable for include() and require()
206
 * @TODO: should we rename this function to PARAM_SAFEDIRS??
207
 */
208
define('PARAM_ALPHAEXT', 0x2000);
209

    
210
/**
211
 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
212
 */
213
define('PARAM_SAFEDIR',  0x4000);
214

    
215
/**
216
 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9.  Numbers and comma only.
217
 */
218
define('PARAM_SEQUENCE',  0x8000);
219

    
220
/**
221
 * PARAM_PEM - Privacy Enhanced Mail format
222
 */
223
define('PARAM_PEM',      0x10000);
224

    
225
/**
226
 * PARAM_BASE64 - Base 64 encoded format
227
 */
228
define('PARAM_BASE64',   0x20000);
229

    
230

    
231
/// Page types ///
232
/**
233
 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
234
 */
235
define('PAGE_COURSE_VIEW', 'course-view');
236

    
237
/// Debug levels ///
238
/** no warnings at all */
239
define ('DEBUG_NONE', 0);
240
/** E_ERROR | E_PARSE */
241
define ('DEBUG_MINIMAL', 5);
242
/** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
243
define ('DEBUG_NORMAL', 15);
244
/** E_ALL without E_STRICT for now, do show recoverable fatal errors */
245
define ('DEBUG_ALL', 6143);
246
/** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
247
define ('DEBUG_DEVELOPER', 38911);
248

    
249
/**
250
 * Blog access level constant declaration
251
 */
252
define ('BLOG_USER_LEVEL', 1);
253
define ('BLOG_GROUP_LEVEL', 2);
254
define ('BLOG_COURSE_LEVEL', 3);
255
define ('BLOG_SITE_LEVEL', 4);
256
define ('BLOG_GLOBAL_LEVEL', 5);
257

    
258
/**
259
 * Tag constanst
260
 */
261
//To prevent problems with multibytes strings, this should not exceed the
262
//length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
263
define('TAG_MAX_LENGTH', 50);
264

    
265
/**
266
 * Password policy constants
267
 */
268
define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
269
define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
270
define ('PASSWORD_DIGITS', '0123456789');
271
define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
272

    
273
if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
274
    define('SORT_LOCALE_STRING', SORT_STRING);
275
}
276

    
277

    
278
/// PARAMETER HANDLING ////////////////////////////////////////////////////
279

    
280
/**
281
 * Returns a particular value for the named variable, taken from
282
 * POST or GET.  If the parameter doesn't exist then an error is
283
 * thrown because we require this variable.
284
 *
285
 * This function should be used to initialise all required values
286
 * in a script that are based on parameters.  Usually it will be
287
 * used like this:
288
 *    $id = required_param('id');
289
 *
290
 * @param string $parname the name of the page parameter we want
291
 * @param int $type expected type of parameter
292
 * @return mixed
293
 */
294
function required_param($parname, $type=PARAM_CLEAN) {
295

    
296
    // detect_unchecked_vars addition
297
    global $CFG;
298
    if (!empty($CFG->detect_unchecked_vars)) {
299
        global $UNCHECKED_VARS;
300
        unset ($UNCHECKED_VARS->vars[$parname]);
301
    }
302

    
303
    if (isset($_POST[$parname])) {       // POST has precedence
304
        $param = $_POST[$parname];
305
    } else if (isset($_GET[$parname])) {
306
        $param = $_GET[$parname];
307
    } else {
308
        error('A required parameter ('.$parname.') was missing');
309
    }
310

    
311
    return clean_param($param, $type);
312
}
313

    
314
/**
315
 * Returns a particular value for the named variable, taken from
316
 * POST or GET, otherwise returning a given default.
317
 *
318
 * This function should be used to initialise all optional values
319
 * in a script that are based on parameters.  Usually it will be
320
 * used like this:
321
 *    $name = optional_param('name', 'Fred');
322
 *
323
 * @param string $parname the name of the page parameter we want
324
 * @param mixed  $default the default value to return if nothing is found
325
 * @param int $type expected type of parameter
326
 * @return mixed
327
 */
328
function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
329

    
330
    // detect_unchecked_vars addition
331
    global $CFG;
332
    if (!empty($CFG->detect_unchecked_vars)) {
333
        global $UNCHECKED_VARS;
334
        unset ($UNCHECKED_VARS->vars[$parname]);
335
    }
336

    
337
    if (isset($_POST[$parname])) {       // POST has precedence
338
        $param = $_POST[$parname];
339
    } else if (isset($_GET[$parname])) {
340
        $param = $_GET[$parname];
341
    } else {
342
        return $default;
343
    }
344

    
345
    return clean_param($param, $type);
346
}
347

    
348
/**
349
 * Used by {@link optional_param()} and {@link required_param()} to
350
 * clean the variables and/or cast to specific types, based on
351
 * an options field.
352
 * <code>
353
 * $course->format = clean_param($course->format, PARAM_ALPHA);
354
 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
355
 * </code>
356
 *
357
 * @uses $CFG
358
 * @uses PARAM_RAW
359
 * @uses PARAM_CLEAN
360
 * @uses PARAM_CLEANHTML
361
 * @uses PARAM_INT
362
 * @uses PARAM_NUMBER
363
 * @uses PARAM_ALPHA
364
 * @uses PARAM_ALPHANUM
365
 * @uses PARAM_ALPHAEXT
366
 * @uses PARAM_SEQUENCE
367
 * @uses PARAM_BOOL
368
 * @uses PARAM_NOTAGS
369
 * @uses PARAM_TEXT
370
 * @uses PARAM_SAFEDIR
371
 * @uses PARAM_CLEANFILE
372
 * @uses PARAM_FILE
373
 * @uses PARAM_PATH
374
 * @uses PARAM_HOST
375
 * @uses PARAM_URL
376
 * @uses PARAM_LOCALURL
377
 * @uses PARAM_PEM
378
 * @uses PARAM_BASE64
379
 * @uses PARAM_TAG
380
 * @uses PARAM_SEQUENCE
381
 * @param mixed $param the variable we are cleaning
382
 * @param int $type expected format of param after cleaning.
383
 * @return mixed
384
 */
385
function clean_param($param, $type) {
386

    
387
    global $CFG;
388

    
389
    if (is_array($param)) {              // Let's loop
390
        $newparam = array();
391
        foreach ($param as $key => $value) {
392
            $newparam[$key] = clean_param($value, $type);
393
        }
394
        return $newparam;
395
    }
396

    
397
    switch ($type) {
398
        case PARAM_RAW:          // no cleaning at all
399
            return $param;
400

    
401
        case PARAM_CLEAN:        // General HTML cleaning, try to use more specific type if possible
402
            if (is_numeric($param)) {
403
                return $param;
404
            }
405
            $param = stripslashes($param);   // Needed for kses to work fine
406
            $param = clean_text($param);     // Sweep for scripts, etc
407
            return addslashes($param);       // Restore original request parameter slashes
408

    
409
        case PARAM_CLEANHTML:    // prepare html fragment for display, do not store it into db!!
410
            $param = stripslashes($param);   // Remove any slashes
411
            $param = clean_text($param);     // Sweep for scripts, etc
412
            return trim($param);
413

    
414
        case PARAM_INT:
415
            return (int)$param;  // Convert to integer
416

    
417
        case PARAM_NUMBER:
418
            return (float)$param;  // Convert to integer
419

    
420
        case PARAM_ALPHA:        // Remove everything not a-z
421
            return eregi_replace('[^a-zA-Z]', '', $param);
422

    
423
        case PARAM_ALPHANUM:     // Remove everything not a-zA-Z0-9
424
            return eregi_replace('[^A-Za-z0-9]', '', $param);
425

    
426
        case PARAM_ALPHAEXT:     // Remove everything not a-zA-Z/_-
427
            return eregi_replace('[^a-zA-Z/_-]', '', $param);
428

    
429
        case PARAM_SEQUENCE:     // Remove everything not 0-9,
430
            return eregi_replace('[^0-9,]', '', $param);
431

    
432
        case PARAM_BOOL:         // Convert to 1 or 0
433
            $tempstr = strtolower($param);
434
            if ($tempstr == 'on' or $tempstr == 'yes' ) {
435
                $param = 1;
436
            } else if ($tempstr == 'off' or $tempstr == 'no') {
437
                $param = 0;
438
            } else {
439
                $param = empty($param) ? 0 : 1;
440
            }
441
            return $param;
442

    
443
        case PARAM_NOTAGS:       // Strip all tags
444
            return strip_tags($param);
445

    
446
        case PARAM_TEXT:    // leave only tags needed for multilang
447
            return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
448

    
449
        case PARAM_SAFEDIR:      // Remove everything not a-zA-Z0-9_-
450
            return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
451

    
452
        case PARAM_CLEANFILE:    // allow only safe characters
453
            return clean_filename($param);
454

    
455
        case PARAM_FILE:         // Strip all suspicious characters from filename
456
            $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
457
            $param = ereg_replace('\.\.+', '', $param);
458
            if($param == '.') {
459
                $param = '';
460
            }
461
            return $param;
462

    
463
        case PARAM_PATH:         // Strip all suspicious characters from file path
464
            $param = str_replace('\\\'', '\'', $param);
465
            $param = str_replace('\\"', '"', $param);
466
            $param = str_replace('\\', '/', $param);
467
            $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
468
            $param = ereg_replace('\.\.+', '', $param);
469
            $param = ereg_replace('//+', '/', $param);
470
            return ereg_replace('/(\./)+', '/', $param);
471

    
472
        case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
473
            $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
474
            // match ipv4 dotted quad
475
            if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
476
                // confirm values are ok
477
                if ( $match[0] > 255
478
                     || $match[1] > 255
479
                     || $match[3] > 255
480
                     || $match[4] > 255 ) {
481
                    // hmmm, what kind of dotted quad is this?
482
                    $param = '';
483
                }
484
            } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
485
                       && !preg_match('/^[\.-]/',  $param) // no leading dots/hyphens
486
                       && !preg_match('/[\.-]$/',  $param) // no trailing dots/hyphens
487
                       ) {
488
                // all is ok - $param is respected
489
            } else {
490
                // all is not ok...
491
                $param='';
492
            }
493
            return $param;
494

    
495
        case PARAM_URL:          // allow safe ftp, http, mailto urls
496
            include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
497
            if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
498
                // all is ok, param is respected
499
            } else {
500
                $param =''; // not really ok
501
            }
502
            return $param;
503

    
504
        case PARAM_LOCALURL:     // allow http absolute, root relative and relative URLs within wwwroot
505
            $param = clean_param($param, PARAM_URL);
506
            if (!empty($param)) {
507
                if (preg_match(':^/:', $param)) {
508
                    // root-relative, ok!
509
                } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
510
                    // absolute, and matches our wwwroot
511
                } else {
512
                    // relative - let's make sure there are no tricks
513
                    if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
514
                        // looks ok.
515
                    } else {
516
                        $param = '';
517
                    }
518
                }
519
            }
520
            return $param;
521

    
522
        case PARAM_PEM:
523
            $param = trim($param);
524
            // PEM formatted strings may contain letters/numbers and the symbols
525
            // forward slash: /
526
            // plus sign:     +
527
            // equal sign:    =
528
            // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
529
            if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
530
                list($wholething, $body) = $matches;
531
                unset($wholething, $matches);
532
                $b64 = clean_param($body, PARAM_BASE64);
533
                if (!empty($b64)) {
534
                    return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
535
                } else {
536
                    return '';
537
                }
538
            }
539
            return '';
540

    
541
        case PARAM_BASE64:
542
            if (!empty($param)) {
543
                // PEM formatted strings may contain letters/numbers and the symbols
544
                // forward slash: /
545
                // plus sign:     +
546
                // equal sign:    =
547
                if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
548
                    return '';
549
                }
550
                $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
551
                // Each line of base64 encoded data must be 64 characters in
552
                // length, except for the last line which may be less than (or
553
                // equal to) 64 characters long.
554
                for ($i=0, $j=count($lines); $i < $j; $i++) {
555
                    if ($i + 1 == $j) {
556
                        if (64 < strlen($lines[$i])) {
557
                            return '';
558
                        }
559
                        continue;
560
                    }
561

    
562
                    if (64 != strlen($lines[$i])) {
563
                        return '';
564
                    }
565
                }
566
                return implode("\n",$lines);
567
            } else {
568
                return '';
569
            }
570

    
571
        case PARAM_TAG:
572
            //as long as magic_quotes_gpc is used, a backslash will be a
573
            //problem, so remove *all* backslash.
574
            $param = str_replace('\\', '', $param);
575
            //convert many whitespace chars into one
576
            $param = preg_replace('/\s+/', ' ', $param);
577
            $textlib = textlib_get_instance();
578
            $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
579
            return $param;
580

    
581

    
582
        case PARAM_TAGLIST:
583
            $tags = explode(',', $param);
584
            $result = array();
585
            foreach ($tags as $tag) {
586
                $res = clean_param($tag, PARAM_TAG);
587
                if ($res != '') {
588
                    $result[] = $res;
589
                }
590
            }
591
            if ($result) {
592
                return implode(',', $result);
593
            } else {
594
                return '';
595
            }
596

    
597
        default:                 // throw error, switched parameters in optional_param or another serious problem
598
            error("Unknown parameter type: $type");
599
    }
600
}
601

    
602
/**
603
 * Return true if given value is integer or string with integer value
604
 *
605
 * @param mixed $value String or Int
606
 * @return bool true if number, false if not
607
 */
608
function is_number($value) {
609
    if (is_int($value)) {
610
        return true;
611
    } else if (is_string($value)) {
612
        return ((string)(int)$value) === $value;
613
    } else {
614
        return false;
615
    }
616
}
617

    
618
/**
619
 * This function is useful for testing whether something you got back from
620
 * the HTML editor actually contains anything. Sometimes the HTML editor
621
 * appear to be empty, but actually you get back a <br> tag or something.
622
 *
623
 * @param string $string a string containing HTML.
624
 * @return boolean does the string contain any actual content - that is text,
625
 * images, objcts, etc.
626
 */
627
function html_is_blank($string) {
628
    return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
629
}
630

    
631
/**
632
 * Set a key in global configuration
633
 *
634
 * Set a key/value pair in both this session's {@link $CFG} global variable
635
 * and in the 'config' database table for future sessions.
636
 *
637
 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
638
 * In that case it doesn't affect $CFG.
639
 *
640
 * A NULL value will delete the entry.
641
 *
642
 * @param string $name the key to set
643
 * @param string $value the value to set (without magic quotes)
644
 * @param string $plugin (optional) the plugin scope
645
 * @uses $CFG
646
 * @return bool
647
 */
648
function set_config($name, $value, $plugin=NULL) {
649
/// No need for get_config because they are usually always available in $CFG
650

    
651
    global $CFG;
652

    
653
    if (empty($plugin)) {
654
        if (!array_key_exists($name, $CFG->config_php_settings)) {
655
            // So it's defined for this invocation at least
656
            if (is_null($value)) {
657
                unset($CFG->$name);
658
            } else {
659
                $CFG->$name = (string)$value; // settings from db are always strings
660
            }
661
        }
662

    
663
        if (get_field('config', 'name', 'name', $name)) {
664
            if ($value===null) {
665
                return delete_records('config', 'name', $name);
666
            } else {
667
                return set_field('config', 'value', addslashes($value), 'name', $name);
668
            }
669
        } else {
670
            if ($value===null) {
671
                return true;
672
            }
673
            $config = new object();
674
            $config->name = $name;
675
            $config->value = addslashes($value);
676
            return insert_record('config', $config);
677
        }
678
    } else { // plugin scope
679
        if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
680
            if ($value===null) {
681
                return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
682
            } else {
683
                return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
684
            }
685
        } else {
686
            if ($value===null) {
687
                return true;
688
            }
689
            $config = new object();
690
            $config->plugin = addslashes($plugin);
691
            $config->name   = $name;
692
            $config->value  = addslashes($value);
693
            return insert_record('config_plugins', $config);
694
        }
695
    }
696
}
697

    
698
/**
699
 * Get configuration values from the global config table
700
 * or the config_plugins table.
701
 *
702
 * If called with no parameters it will do the right thing
703
 * generating $CFG safely from the database without overwriting
704
 * existing values.
705
 *
706
 * If called with 2 parameters it will return a $string single
707
 * value or false of the value is not found.
708
 *
709
 * @param string $plugin
710
 * @param string $name
711
 * @uses $CFG
712
 * @return hash-like object or single value
713
 *
714
 */
715
function get_config($plugin=NULL, $name=NULL) {
716

    
717
    global $CFG;
718

    
719
    if (!empty($name)) { // the user is asking for a specific value
720
        if (!empty($plugin)) {
721
            return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
722
        } else {
723
            return get_field('config', 'value', 'name', $name);
724
        }
725
    }
726

    
727
    // the user is after a recordset
728
    if (!empty($plugin)) {
729
        if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
730
            $configs = (array)$configs;
731
            $localcfg = array();
732
            foreach ($configs as $config) {
733
                $localcfg[$config->name] = $config->value;
734
            }
735
            return (object)$localcfg;
736
        } else {
737
            return false;
738
        }
739
    } else {
740
        // this was originally in setup.php
741
        if ($configs = get_records('config')) {
742
            $localcfg = (array)$CFG;
743
            foreach ($configs as $config) {
744
                if (!isset($localcfg[$config->name])) {
745
                    $localcfg[$config->name] = $config->value;
746
                }
747
                // do not complain anymore if config.php overrides settings from db
748
            }
749

    
750
            $localcfg = (object)$localcfg;
751
            return $localcfg;
752
        } else {
753
            // preserve $CFG if DB returns nothing or error
754
            return $CFG;
755
        }
756

    
757
    }
758
}
759

    
760
/**
761
 * Removes a key from global configuration
762
 *
763
 * @param string $name the key to set
764
 * @param string $plugin (optional) the plugin scope
765
 * @uses $CFG
766
 * @return bool
767
 */
768
function unset_config($name, $plugin=NULL) {
769

    
770
    global $CFG;
771

    
772
    unset($CFG->$name);
773

    
774
    if (empty($plugin)) {
775
        return delete_records('config', 'name', $name);
776
    } else {
777
        return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
778
    }
779
}
780

    
781
/**
782
 * Get volatile flags
783
 *
784
 * @param string $type
785
 * @param int    $changedsince
786
 * @return records array
787
 *
788
 */
789
function get_cache_flags($type, $changedsince=NULL) {
790

    
791
    $type = addslashes($type);
792

    
793
    $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
794
    if ($changedsince !== NULL) {
795
        $changedsince = (int)$changedsince;
796
        $sqlwhere .= ' AND timemodified > ' . $changedsince;
797
    }
798
    $cf = array();
799
    if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
800
        foreach ($flags as $flag) {
801
            $cf[$flag->name] = $flag->value;
802
        }
803
    }
804
    return $cf;
805
}
806

    
807
/**
808
 * Use this funciton to get a list of users from a config setting of type admin_setting_users_with_capability.
809
 * @param string $value the value of the config setting.
810
 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
811
 * @return array of user objects.
812
 */
813
function get_users_from_config($value, $capability) {
814
    global $CFG;
815
    if ($value == '$@ALL@$') {
816
        $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
817
    } else if ($value) {
818
        $usernames = explode(',', $value);
819
        $users = get_records_select('user', "username IN ('" . implode("','", $usernames) . "') AND mnethostid = " . $CFG->mnet_localhost_id);
820
    } else {
821
        $users = array();
822
    }
823
    return $users;
824
}
825

    
826
/**
827
 * Get volatile flags
828
 *
829
 * @param string $type
830
 * @param string $name
831
 * @param int    $changedsince
832
 * @return records array
833
 *
834
 */
835
function get_cache_flag($type, $name, $changedsince=NULL) {
836

    
837
    $type = addslashes($type);
838
    $name = addslashes($name);
839

    
840
    $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
841
    if ($changedsince !== NULL) {
842
        $changedsince = (int)$changedsince;
843
        $sqlwhere .= ' AND timemodified > ' . $changedsince;
844
    }
845
    return get_field_select('cache_flags', 'value', $sqlwhere);
846
}
847

    
848
/**
849
 * Set a volatile flag
850
 *
851
 * @param string $type the "type" namespace for the key
852
 * @param string $name the key to set
853
 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
854
 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
855
 * @return bool
856
 */
857
function set_cache_flag($type, $name, $value, $expiry=NULL) {
858

    
859

    
860
    $timemodified = time();
861
    if ($expiry===NULL || $expiry < $timemodified) {
862
        $expiry = $timemodified + 24 * 60 * 60;
863
    } else {
864
        $expiry = (int)$expiry;
865
    }
866

    
867
    if ($value === NULL) {
868
        return unset_cache_flag($type,$name);
869
    }
870

    
871
    $type = addslashes($type);
872
    $name = addslashes($name);
873
    if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
874
        if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
875
            return true; //no need to update; helps rcache too
876
        }
877
        $f->value        = addslashes($value);
878
        $f->expiry       = $expiry;
879
        $f->timemodified = $timemodified;
880
        return update_record('cache_flags', $f);
881
    } else {
882
        $f = new object();
883
        $f->flagtype     = $type;
884
        $f->name         = $name;
885
        $f->value        = addslashes($value);
886
        $f->expiry       = $expiry;
887
        $f->timemodified = $timemodified;
888
        return (bool)insert_record('cache_flags', $f);
889
    }
890
}
891

    
892
/**
893
 * Removes a single volatile flag
894
 *
895
 * @param string $type the "type" namespace for the key
896
 * @param string $name the key to set
897
 * @uses $CFG
898
 * @return bool
899
 */
900
function unset_cache_flag($type, $name) {
901

    
902
    return delete_records('cache_flags',
903
                          'name', addslashes($name),
904
                          'flagtype', addslashes($type));
905
}
906

    
907
/**
908
 * Garbage-collect volatile flags
909
 *
910
 */
911
function gc_cache_flags() {
912
    return delete_records_select('cache_flags', 'expiry < ' . time());
913
}
914

    
915
/**
916
 * Refresh current $USER session global variable with all their current preferences.
917
 * @uses $USER
918
 */
919
function reload_user_preferences() {
920

    
921
    global $USER;
922

    
923
    //reset preference
924
    $USER->preference = array();
925

    
926
    if (!isloggedin() or isguestuser()) {
927
        // no permanent storage for not-logged-in user and guest
928

    
929
    } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
930
        foreach ($preferences as $preference) {
931
            $USER->preference[$preference->name] = $preference->value;
932
        }
933
    }
934

    
935
    return true;
936
}
937

    
938
/**
939
 * Sets a preference for the current user
940
 * Optionally, can set a preference for a different user object
941
 * @uses $USER
942
 * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
943

944
 * @param string $name The key to set as preference for the specified user
945
 * @param string $value The value to set forthe $name key in the specified user's record
946
 * @param int $otheruserid A moodle user ID
947
 * @return bool
948
 */
949
function set_user_preference($name, $value, $otheruserid=NULL) {
950

    
951
    global $USER;
952

    
953
    if (!isset($USER->preference)) {
954
        reload_user_preferences();
955
    }
956

    
957
    if (empty($name)) {
958
        return false;
959
    }
960

    
961
    $nostore = false;
962

    
963
    if (empty($otheruserid)){
964
        if (!isloggedin() or isguestuser()) {
965
            $nostore = true;
966
        }
967
        $userid = $USER->id;
968
    } else {
969
        if (isguestuser($otheruserid)) {
970
            $nostore = true;
971
        }
972
        $userid = $otheruserid;
973
    }
974

    
975
    $return = true;
976
    if ($nostore) {
977
        // no permanent storage for not-logged-in user and guest
978

    
979
    } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
980
        if ($preference->value === $value) {
981
            return true;
982
        }
983
        if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
984
            $return = false;
985
        }
986

    
987
    } else {
988
        $preference = new object();
989
        $preference->userid = $userid;
990
        $preference->name   = addslashes($name);
991
        $preference->value  = addslashes((string)$value);
992
        if (!insert_record('user_preferences', $preference)) {
993
            $return = false;
994
        }
995
    }
996

    
997
    // update value in USER session if needed
998
    if ($userid == $USER->id) {
999
        $USER->preference[$name] = (string)$value;
1000
    }
1001

    
1002
    return $return;
1003
}
1004

    
1005
/**
1006
 * Unsets a preference completely by deleting it from the database
1007
 * Optionally, can set a preference for a different user id
1008
 * @uses $USER
1009
 * @param string  $name The key to unset as preference for the specified user
1010
 * @param int $otheruserid A moodle user ID
1011
 */
1012
function unset_user_preference($name, $otheruserid=NULL) {
1013

    
1014
    global $USER;
1015

    
1016
    if (!isset($USER->preference)) {
1017
        reload_user_preferences();
1018
    }
1019

    
1020
    if (empty($otheruserid)){
1021
        $userid = $USER->id;
1022
    } else {
1023
        $userid = $otheruserid;
1024
    }
1025

    
1026
    //Delete the preference from $USER if needed
1027
    if ($userid == $USER->id) {
1028
        unset($USER->preference[$name]);
1029
    }
1030

    
1031
    //Then from DB
1032
    return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
1033
}
1034

    
1035

    
1036
/**
1037
 * Sets a whole array of preferences for the current user
1038
 * @param array $prefarray An array of key/value pairs to be set
1039
 * @param int $otheruserid A moodle user ID
1040
 * @return bool
1041
 */
1042
function set_user_preferences($prefarray, $otheruserid=NULL) {
1043

    
1044
    if (!is_array($prefarray) or empty($prefarray)) {
1045
        return false;
1046
    }
1047

    
1048
    $return = true;
1049
    foreach ($prefarray as $name => $value) {
1050
        // The order is important; test for return is done first
1051
        $return = (set_user_preference($name, $value, $otheruserid) && $return);
1052
    }
1053
    return $return;
1054
}
1055

    
1056
/**
1057
 * If no arguments are supplied this function will return
1058
 * all of the current user preferences as an array.
1059
 * If a name is specified then this function
1060
 * attempts to return that particular preference value.  If
1061
 * none is found, then the optional value $default is returned,
1062
 * otherwise NULL.
1063
 * @param string $name Name of the key to use in finding a preference value
1064
 * @param string $default Value to be returned if the $name key is not set in the user preferences
1065
 * @param int $otheruserid A moodle user ID
1066
 * @uses $USER
1067
 * @return string
1068
 */
1069
function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
1070
    global $USER;
1071

    
1072
    if (!isset($USER->preference)) {
1073
        reload_user_preferences();
1074
    }
1075

    
1076
    if (empty($otheruserid)){
1077
        $userid = $USER->id;
1078
    } else {
1079
        $userid = $otheruserid;
1080
    }
1081

    
1082
    if ($userid == $USER->id) {
1083
        $preference = $USER->preference;
1084

    
1085
    } else {
1086
        $preference = array();
1087
        if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
1088
            foreach ($prefdata as $pref) {
1089
                $preference[$pref->name] = $pref->value;
1090
            }
1091
        }
1092
    }
1093

    
1094
    if (empty($name)) {
1095
        return $preference;            // All values
1096

    
1097
    } else if (array_key_exists($name, $preference)) {
1098
        return $preference[$name];    // The single value
1099

    
1100
    } else {
1101
        return $default;              // Default value (or NULL)
1102
    }
1103
}
1104

    
1105

    
1106
/// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1107

    
1108
/**
1109
 * Given date parts in user time produce a GMT timestamp.
1110
 *
1111
 * @param int $year The year part to create timestamp of
1112
 * @param int $month The month part to create timestamp of
1113
 * @param int $day The day part to create timestamp of
1114
 * @param int $hour The hour part to create timestamp of
1115
 * @param int $minute The minute part to create timestamp of
1116
 * @param int $second The second part to create timestamp of
1117
 * @param float $timezone ?
1118
 * @param bool $applydst ?
1119
 * @return int timestamp
1120
 * @todo Finish documenting this function
1121
 */
1122
function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1123

    
1124
    $strtimezone = NULL;
1125
    if (!is_numeric($timezone)) {
1126
        $strtimezone = $timezone;
1127
    }
1128

    
1129
    $timezone = get_user_timezone_offset($timezone);
1130

    
1131
    if (abs($timezone) > 13) {
1132
        $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1133
    } else {
1134
        $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1135
        $time = usertime($time, $timezone);
1136
        if($applydst) {
1137
            $time -= dst_offset_on($time, $strtimezone);
1138
        }
1139
    }
1140

    
1141
    return $time;
1142

    
1143
}
1144

    
1145
/**
1146
 * Given an amount of time in seconds, returns string
1147
 * formatted nicely as weeks, days, hours etc as needed
1148
 *
1149
 * @uses MINSECS
1150
 * @uses HOURSECS
1151
 * @uses DAYSECS
1152
 * @uses YEARSECS
1153
 * @param int $totalsecs ?
1154
 * @param array $str ?
1155
 * @return string
1156
 */
1157
 function format_time($totalsecs, $str=NULL) {
1158

    
1159
    $totalsecs = abs($totalsecs);
1160

    
1161
    if (!$str) {  // Create the str structure the slow way
1162
        $str->day   = get_string('day');
1163
        $str->days  = get_string('days');
1164
        $str->hour  = get_string('hour');
1165
        $str->hours = get_string('hours');
1166
        $str->min   = get_string('min');
1167
        $str->mins  = get_string('mins');
1168
        $str->sec   = get_string('sec');
1169
        $str->secs  = get_string('secs');
1170
        $str->year  = get_string('year');
1171
        $str->years = get_string('years');
1172
    }
1173

    
1174

    
1175
    $years     = floor($totalsecs/YEARSECS);
1176
    $remainder = $totalsecs - ($years*YEARSECS);
1177
    $days      = floor($remainder/DAYSECS);
1178
    $remainder = $totalsecs - ($days*DAYSECS);
1179
    $hours     = floor($remainder/HOURSECS);
1180
    $remainder = $remainder - ($hours*HOURSECS);
1181
    $mins      = floor($remainder/MINSECS);
1182
    $secs      = $remainder - ($mins*MINSECS);
1183

    
1184
    $ss = ($secs == 1)  ? $str->sec  : $str->secs;
1185
    $sm = ($mins == 1)  ? $str->min  : $str->mins;
1186
    $sh = ($hours == 1) ? $str->hour : $str->hours;
1187
    $sd = ($days == 1)  ? $str->day  : $str->days;
1188
    $sy = ($years == 1)  ? $str->year  : $str->years;
1189

    
1190
    $oyears = '';
1191
    $odays = '';
1192
    $ohours = '';
1193
    $omins = '';
1194
    $osecs = '';
1195

    
1196
    if ($years)  $oyears  = $years .' '. $sy;
1197
    if ($days)  $odays  = $days .' '. $sd;
1198
    if ($hours) $ohours = $hours .' '. $sh;
1199
    if ($mins)  $omins  = $mins .' '. $sm;
1200
    if ($secs)  $osecs  = $secs .' '. $ss;
1201

    
1202
    if ($years) return trim($oyears .' '. $odays);
1203
    if ($days)  return trim($odays .' '. $ohours);
1204
    if ($hours) return trim($ohours .' '. $omins);
1205
    if ($mins)  return trim($omins .' '. $osecs);
1206
    if ($secs)  return $osecs;
1207
    return get_string('now');
1208
}
1209

    
1210
/**
1211
 * Returns a formatted string that represents a date in user time
1212
 * <b>WARNING: note that the format is for strftime(), not date().</b>
1213
 * Because of a bug in most Windows time libraries, we can't use
1214
 * the nicer %e, so we have to use %d which has leading zeroes.
1215
 * A lot of the fuss in the function is just getting rid of these leading
1216
 * zeroes as efficiently as possible.
1217
 *
1218
 * If parameter fixday = true (default), then take off leading
1219
 * zero from %d, else mantain it.
1220
 *
1221
 * @uses HOURSECS
1222
 * @param  int $date timestamp in GMT
1223
 * @param string $format strftime format
1224
 * @param float $timezone
1225
 * @param bool $fixday If true (default) then the leading
1226
 * zero from %d is removed. If false then the leading zero is mantained.
1227
 * @return string
1228
 */
1229
function userdate($date, $format='', $timezone=99, $fixday = true) {
1230

    
1231
    global $CFG;
1232

    
1233
    $strtimezone = NULL;
1234
    if (!is_numeric($timezone)) {
1235
        $strtimezone = $timezone;
1236
    }
1237

    
1238
    if (empty($format)) {
1239
        $format = get_string('strftimedaydatetime');
1240
    }
1241

    
1242
    if (!empty($CFG->nofixday)) {  // Config.php can force %d not to be fixed.
1243
        $fixday = false;
1244
    } else if ($fixday) {
1245
        $formatnoday = str_replace('%d', 'DD', $format);
1246
        $fixday = ($formatnoday != $format);
1247
    }
1248

    
1249
    $date += dst_offset_on($date, $strtimezone);
1250

    
1251
    $timezone = get_user_timezone_offset($timezone);
1252

    
1253
    if (abs($timezone) > 13) {   /// Server time
1254
        if ($fixday) {
1255
            $datestring = strftime($formatnoday, $date);
1256
            $daystring  = str_replace(' 0', '', strftime(' %d', $date));
1257
            $datestring = str_replace('DD', $daystring, $datestring);
1258
        } else {
1259
            $datestring = strftime($format, $date);
1260
        }
1261
    } else {
1262
        $date += (int)($timezone * 3600);
1263
        if ($fixday) {
1264
            $datestring = gmstrftime($formatnoday, $date);
1265
            $daystring  = str_replace(' 0', '', gmstrftime(' %d', $date));
1266
            $datestring = str_replace('DD', $daystring, $datestring);
1267
        } else {
1268
            $datestring = gmstrftime($format, $date);
1269
        }
1270
    }
1271

    
1272
/// If we are running under Windows convert from windows encoding to UTF-8
1273
/// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1274

    
1275
   if ($CFG->ostype == 'WINDOWS') {
1276
       if ($localewincharset = get_string('localewincharset')) {
1277
           $textlib = textlib_get_instance();
1278
           $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1279
       }
1280
   }
1281

    
1282
    return $datestring;
1283
}
1284

    
1285
/**
1286
 * Given a $time timestamp in GMT (seconds since epoch),
1287
 * returns an array that represents the date in user time
1288
 *
1289
 * @uses HOURSECS
1290
 * @param int $time Timestamp in GMT
1291
 * @param float $timezone ?
1292
 * @return array An array that represents the date in user time
1293
 * @todo Finish documenting this function
1294
 */
1295
function usergetdate($time, $timezone=99) {
1296

    
1297
    $strtimezone = NULL;
1298
    if (!is_numeric($timezone)) {
1299
        $strtimezone = $timezone;
1300
    }
1301

    
1302
    $timezone = get_user_timezone_offset($timezone);
1303

    
1304
    if (abs($timezone) > 13) {    // Server time
1305
        return getdate($time);
1306
    }
1307

    
1308
    // There is no gmgetdate so we use gmdate instead
1309
    $time += dst_offset_on($time, $strtimezone);
1310
    $time += intval((float)$timezone * HOURSECS);
1311

    
1312
    $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
1313

    
1314
    //be careful to ensure the returned array matches that produced by getdate() above
1315
    list(
1316
        $getdate['month'],
1317
        $getdate['weekday'],
1318
        $getdate['yday'],
1319
        $getdate['year'],
1320
        $getdate['mon'],
1321
        $getdate['wday'],
1322
        $getdate['mday'],
1323
        $getdate['hours'],
1324
        $getdate['minutes'],
1325
        $getdate['seconds']
1326
    ) = explode('_', $datestring);
1327

    
1328
    return $getdate;
1329
}
1330

    
1331
/**
1332
 * Given a GMT timestamp (seconds since epoch), offsets it by
1333
 * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1334
 *
1335
 * @uses HOURSECS
1336
 * @param  int $date Timestamp in GMT
1337
 * @param float $timezone
1338
 * @return int
1339
 */
1340
function usertime($date, $timezone=99) {
1341

    
1342
    $timezone = get_user_timezone_offset($timezone);
1343

    
1344
    if (abs($timezone) > 13) {
1345
        return $date;
1346
    }
1347
    return $date - (int)($timezone * HOURSECS);
1348
}
1349

    
1350
/**
1351
 * Given a time, return the GMT timestamp of the most recent midnight
1352
 * for the current user.
1353
 *
1354
 * @param int $date Timestamp in GMT
1355
 * @param float $timezone ?
1356
 * @return ?
1357
 */
1358
function usergetmidnight($date, $timezone=99) {
1359

    
1360
    $userdate = usergetdate($date, $timezone);
1361

    
1362
    // Time of midnight of this user's day, in GMT
1363
    return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1364

    
1365
}
1366

    
1367
/**
1368
 * Returns a string that prints the user's timezone
1369
 *
1370
 * @param float $timezone The user's timezone
1371
 * @return string
1372
 */
1373
function usertimezone($timezone=99) {
1374

    
1375
    $tz = get_user_timezone($timezone);
1376

    
1377
    if (!is_float($tz)) {
1378
        return $tz;
1379
    }
1380

    
1381
    if(abs($tz) > 13) { // Server time
1382
        return get_string('serverlocaltime');
1383
    }
1384

    
1385
    if($tz == intval($tz)) {
1386
        // Don't show .0 for whole hours
1387
        $tz = intval($tz);
1388
    }
1389

    
1390
    if($tz == 0) {
1391
        return 'UTC';
1392
    }
1393
    else if($tz > 0) {
1394
        return 'UTC+'.$tz;
1395
    }
1396
    else {
1397
        return 'UTC'.$tz;
1398
    }
1399

    
1400
}
1401

    
1402
/**
1403
 * Returns a float which represents the user's timezone difference from GMT in hours
1404
 * Checks various settings and picks the most dominant of those which have a value
1405
 *
1406
 * @uses $CFG
1407
 * @uses $USER
1408
 * @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
1409
 * @return int
1410
 */
1411
function get_user_timezone_offset($tz = 99) {
1412

    
1413
    global $USER, $CFG;
1414

    
1415
    $tz = get_user_timezone($tz);
1416

    
1417
    if (is_float($tz)) {
1418
        return $tz;
1419
    } else {
1420
        $tzrecord = get_timezone_record($tz);
1421
        if (empty($tzrecord)) {
1422
            return 99.0;
1423
        }
1424
        return (float)$tzrecord->gmtoff / HOURMINS;
1425
    }
1426
}
1427

    
1428
/**
1429
 * Returns an int which represents the systems's timezone difference from GMT in seconds
1430
 * @param mixed $tz timezone
1431
 * @return int if found, false is timezone 99 or error
1432
 */
1433
function get_timezone_offset($tz) {
1434
    global $CFG;
1435

    
1436
    if ($tz == 99) {
1437
        return false;
1438
    }
1439

    
1440
    if (is_numeric($tz)) {
1441
        return intval($tz * 60*60);
1442
    }
1443

    
1444
    if (!$tzrecord = get_timezone_record($tz)) {
1445
        return false;
1446
    }
1447
    return intval($tzrecord->gmtoff * 60);
1448
}
1449

    
1450
/**
1451
 * Returns a float or a string which denotes the user's timezone
1452
 * 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)
1453
 * means that for this timezone there are also DST rules to be taken into account
1454
 * Checks various settings and picks the most dominant of those which have a value
1455
 *
1456
 * @uses $USER
1457
 * @uses $CFG
1458
 * @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
1459
 * @return mixed
1460
 */
1461
function get_user_timezone($tz = 99) {
1462
    global $USER, $CFG;
1463

    
1464
    $timezones = array(
1465
        $tz,
1466
        isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1467
        isset($USER->timezone) ? $USER->timezone : 99,
1468
        isset($CFG->timezone) ? $CFG->timezone : 99,
1469
        );
1470

    
1471
    $tz = 99;
1472

    
1473
    while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
1474
        $tz = $next['value'];
1475
    }
1476

    
1477
    return is_numeric($tz) ? (float) $tz : $tz;
1478
}
1479

    
1480
/**
1481
 * ?
1482
 *
1483
 * @uses $CFG
1484
 * @uses $db
1485
 * @param string $timezonename ?
1486
 * @return object
1487
 */
1488
function get_timezone_record($timezonename) {
1489
    global $CFG, $db;
1490
    static $cache = NULL;
1491

    
1492
    if ($cache === NULL) {
1493
        $cache = array();
1494
    }
1495

    
1496
    if (isset($cache[$timezonename])) {
1497
        return $cache[$timezonename];
1498
    }
1499

    
1500
    return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1501
                                      WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1502
}
1503

    
1504
/**
1505
 * ?
1506
 *
1507
 * @uses $CFG
1508
 * @uses $USER
1509
 * @param ? $fromyear ?
1510
 * @param ? $to_year ?
1511
 * @return bool
1512
 */
1513
function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1514
    global $CFG, $SESSION;
1515

    
1516
    $usertz = get_user_timezone($strtimezone);
1517

    
1518
    if (is_float($usertz)) {
1519
        // Trivial timezone, no DST
1520
        return false;
1521
    }
1522

    
1523
    if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1524
        // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1525
        unset($SESSION->dst_offsets);
1526
        unset($SESSION->dst_range);
1527
    }
1528

    
1529
    if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1530
        // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1531
        // This will be the return path most of the time, pretty light computationally
1532
        return true;
1533
    }
1534

    
1535
    // Reaching here means we either need to extend our table or create it from scratch
1536

    
1537
    // Remember which TZ we calculated these changes for
1538
    $SESSION->dst_offsettz = $usertz;
1539

    
1540
    if(empty($SESSION->dst_offsets)) {
1541
        // If we 're creating from scratch, put the two guard elements in there
1542
        $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1543
    }
1544
    if(empty($SESSION->dst_range)) {
1545
        // If creating from scratch
1546
        $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1547
        $to   = min((empty($to_year)   ? intval(date('Y')) + 3 : $to_year),   2035);
1548

    
1549
        // Fill in the array with the extra years we need to process
1550
        $yearstoprocess = array();
1551
        for($i = $from; $i <= $to; ++$i) {
1552
            $yearstoprocess[] = $i;
1553
        }
1554

    
1555
        // Take note of which years we have processed for future calls
1556
        $SESSION->dst_range = array($from, $to);
1557
    }
1558
    else {
1559
        // If needing to extend the table, do the same
1560
        $yearstoprocess = array();
1561

    
1562
        $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1563
        $to   = min((empty($to_year)   ? $SESSION->dst_range[1] : $to_year),   2035);
1564

    
1565
        if($from < $SESSION->dst_range[0]) {
1566
            // Take note of which years we need to process and then note that we have processed them for future calls
1567
            for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1568
                $yearstoprocess[] = $i;
1569
            }
1570
            $SESSION->dst_range[0] = $from;
1571
        }
1572
        if($to > $SESSION->dst_range[1]) {
1573
            // Take note of which years we need to process and then note that we have processed them for future calls
1574
            for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1575
                $yearstoprocess[] = $i;
1576
            }
1577
            $SESSION->dst_range[1] = $to;
1578
        }
1579
    }
1580

    
1581
    if(empty($yearstoprocess)) {
1582
        // This means that there was a call requesting a SMALLER range than we have already calculated
1583
        return true;
1584
    }
1585

    
1586
    // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1587
    // Also, the array is sorted in descending timestamp order!
1588

    
1589
    // Get DB data
1590

    
1591
    static $presets_cache = array();
1592
    if (!isset($presets_cache[$usertz])) {
1593
        $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');
1594
    }
1595
    if(empty($presets_cache[$usertz])) {
1596
        return false;
1597
    }
1598

    
1599
    // Remove ending guard (first element of the array)
1600
    reset($SESSION->dst_offsets);
1601
    unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1602

    
1603
    // Add all required change timestamps
1604
    foreach($yearstoprocess as $y) {
1605
        // Find the record which is in effect for the year $y
1606
        foreach($presets_cache[$usertz] as $year => $preset) {
1607
            if($year <= $y) {
1608
                break;
1609
            }
1610
        }
1611

    
1612
        $changes = dst_changes_for_year($y, $preset);
1613

    
1614
        if($changes === NULL) {
1615
            continue;
1616
        }
1617
        if($changes['dst'] != 0) {
1618
            $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1619
        }
1620
        if($changes['std'] != 0) {
1621
            $SESSION->dst_offsets[$changes['std']] = 0;
1622
        }
1623
    }
1624

    
1625
    // Put in a guard element at the top
1626
    $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1627
    $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1628

    
1629
    // Sort again
1630
    krsort($SESSION->dst_offsets);
1631

    
1632
    return true;
1633
}
1634

    
1635
function dst_changes_for_year($year, $timezone) {
1636

    
1637
    if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1638
        return NULL;
1639
    }
1640

    
1641
    $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1642
    $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1643

    
1644
    list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1645
    list($std_hour, $std_min) = explode(':', $timezone->std_time);
1646

    
1647
    $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1648
    $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1649

    
1650
    // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1651
    // This has the advantage of being able to have negative values for hour, i.e. for timezones
1652
    // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1653

    
1654
    $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1655
    $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1656

    
1657
    return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1658
}
1659

    
1660
// $time must NOT be compensated at all, it has to be a pure timestamp
1661
function dst_offset_on($time, $strtimezone = NULL) {
1662
    global $SESSION;
1663

    
1664
    if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
1665
        return 0;
1666
    }
1667

    
1668
    reset($SESSION->dst_offsets);
1669
    while(list($from, $offset) = each($SESSION->dst_offsets)) {
1670
        if($from <= $time) {
1671
            break;
1672
        }
1673
    }
1674

    
1675
    // This is the normal return path
1676
    if($offset !== NULL) {
1677
        return $offset;
1678
    }
1679

    
1680
    // Reaching this point means we haven't calculated far enough, do it now:
1681
    // Calculate extra DST changes if needed and recurse. The recursion always
1682
    // moves toward the stopping condition, so will always end.
1683

    
1684
    if($from == 0) {
1685
        // We need a year smaller than $SESSION->dst_range[0]
1686
        if($SESSION->dst_range[0] == 1971) {
1687
            return 0;
1688
        }
1689
        calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
1690
        return dst_offset_on($time, $strtimezone);
1691
    }
1692
    else {
1693
        // We need a year larger than $SESSION->dst_range[1]
1694
        if($SESSION->dst_range[1] == 2035) {
1695
            return 0;
1696
        }
1697
        calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
1698
        return dst_offset_on($time, $strtimezone);
1699
    }
1700
}
1701

    
1702
function find_day_in_month($startday, $weekday, $month, $year) {
1703

    
1704
    $daysinmonth = days_in_month($month, $year);
1705

    
1706
    if($weekday == -1) {
1707
        // Don't care about weekday, so return:
1708
        //    abs($startday) if $startday != -1
1709
        //    $daysinmonth otherwise
1710
        return ($startday == -1) ? $daysinmonth : abs($startday);
1711
    }
1712

    
1713
    // From now on we 're looking for a specific weekday
1714

    
1715
    // Give "end of month" its actual value, since we know it
1716
    if($startday == -1) {
1717
        $startday = -1 * $daysinmonth;
1718
    }
1719

    
1720
    // Starting from day $startday, the sign is the direction
1721

    
1722
    if($startday < 1) {
1723

    
1724
        $startday = abs($startday);
1725
        $lastmonthweekday  = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1726

    
1727
        // This is the last such weekday of the month
1728
        $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1729
        if($lastinmonth > $daysinmonth) {
1730
            $lastinmonth -= 7;
1731
        }
1732

    
1733
        // Find the first such weekday <= $startday
1734
        while($lastinmonth > $startday) {
1735
            $lastinmonth -= 7;
1736
        }
1737

    
1738
        return $lastinmonth;
1739

    
1740
    }
1741
    else {
1742

    
1743
        $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1744

    
1745
        $diff = $weekday - $indexweekday;
1746
        if($diff < 0) {
1747
            $diff += 7;
1748
        }
1749

    
1750
        // This is the first such weekday of the month equal to or after $startday
1751
        $firstfromindex = $startday + $diff;
1752

    
1753
        return $firstfromindex;
1754

    
1755
    }
1756
}
1757

    
1758
/**
1759
 * Calculate the number of days in a given month
1760
 *
1761
 * @param int $month The month whose day count is sought
1762
 * @param int $year The year of the month whose day count is sought
1763
 * @return int
1764
 */
1765
function days_in_month($month, $year) {
1766
   return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1767
}
1768

    
1769
/**
1770
 * Calculate the position in the week of a specific calendar day
1771
 *
1772
 * @param int $day The day of the date whose position in the week is sought
1773
 * @param int $month The month of the date whose position in the week is sought
1774
 * @param int $year The year of the date whose position in the week is sought
1775
 * @return int
1776
 */
1777
function dayofweek($day, $month, $year) {
1778
    // I wonder if this is any different from
1779
    // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1780
    return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1781
}
1782

    
1783
/// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1784

    
1785
/**
1786
 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1787
 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1788
 * sesskey string if $USER exists, or boolean false if not.
1789
 *
1790
 * @uses $USER
1791
 * @return string
1792
 */
1793
function sesskey() {
1794
    global $USER;
1795

    
1796
    if(!isset($USER)) {
1797
        return false;
1798
    }
1799

    
1800
    if (empty($USER->sesskey)) {
1801
        $USER->sesskey = random_string(10);
1802
    }
1803

    
1804
    return $USER->sesskey;
1805
}
1806

    
1807

    
1808
/**
1809
 * For security purposes, this function will check that the currently
1810
 * given sesskey (passed as a parameter to the script or this function)
1811
 * matches that of the current user.
1812
 *
1813
 * @param string $sesskey optionally provided sesskey
1814
 * @return bool
1815
 */
1816
function confirm_sesskey($sesskey=NULL) {
1817
    global $USER;
1818

    
1819
    if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1820
        return true;
1821
    }
1822

    
1823
    if (empty($sesskey)) {
1824
        $sesskey = required_param('sesskey', PARAM_RAW);  // Check script parameters
1825
    }
1826

    
1827
    if (!isset($USER->sesskey)) {
1828
        return false;
1829
    }
1830

    
1831
    return ($USER->sesskey === $sesskey);
1832
}
1833

    
1834
/**
1835
 * Check the session key using {@link confirm_sesskey()},
1836
 * and cause a fatal error if it does not match.
1837
 */
1838
function require_sesskey() {
1839
    if (!confirm_sesskey()) {
1840
        print_error('invalidsesskey');
1841
    }
1842
}
1843

    
1844
/**
1845
 * Setup all global $CFG course variables, set locale and also themes
1846
 * This function can be used on pages that do not require login instead of require_login()
1847
 *
1848
 * @param mixed $courseorid id of the course or course object
1849
 */
1850
function course_setup($courseorid=0) {
1851
    global $COURSE, $CFG, $SITE;
1852

    
1853
/// Redefine global $COURSE if needed
1854
    if (empty($courseorid)) {
1855
        // no change in global $COURSE - for backwards compatibiltiy
1856
        // if require_rogin() used after require_login($courseid);
1857
    } else if (is_object($courseorid)) {
1858
        $COURSE = clone($courseorid);
1859
    } else {
1860
        global $course; // used here only to prevent repeated fetching from DB - may be removed later
1861
        if ($courseorid == SITEID) {
1862
            $COURSE = clone($SITE);
1863
        } else if (!empty($course->id) and $course->id == $courseorid) {
1864
            $COURSE = clone($course);
1865
        } else {
1866
            if (!$COURSE = get_record('course', 'id', $courseorid)) {
1867
                error('Invalid course ID');
1868
            }
1869
        }
1870
    }
1871

    
1872
/// set locale and themes
1873
    moodle_setlocale();
1874
    theme_setup();
1875

    
1876
}
1877

    
1878
/**
1879
 * This function checks that the current user is logged in and has the
1880
 * required privileges
1881
 *
1882
 * This function checks that the current user is logged in, and optionally
1883
 * whether they are allowed to be in a particular course and view a particular
1884
 * course module.
1885
 * If they are not logged in, then it redirects them to the site login unless
1886
 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1887
 * case they are automatically logged in as guests.
1888
 * If $courseid is given and the user is not enrolled in that course then the
1889
 * user is redirected to the course enrolment page.
1890
 * If $cm is given and the coursemodule is hidden and the user is not a teacher
1891
 * in the course then the user is redirected to the course home page.
1892
 *
1893
 * @uses $CFG
1894
 * @uses $SESSION
1895
 * @uses $USER
1896
 * @uses $FULLME
1897
 * @uses SITEID
1898
 * @uses $COURSE
1899
 * @param mixed $courseorid id of the course or course object
1900
 * @param bool $autologinguest
1901
 * @param object $cm course module object
1902
 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
1903
 *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
1904
 *             in order to keep redirects working properly. MDL-14495
1905
 */
1906
function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
1907

    
1908
    global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1909

    
1910
/// setup global $COURSE, themes, language and locale
1911
    course_setup($courseorid);
1912

    
1913
/// If the user is not even logged in yet then make sure they are
1914
    if (!isloggedin()) {
1915
        //NOTE: $USER->site check was obsoleted by session test cookie,
1916
        //      $USER->confirmed test is in login/index.php
1917
        if ($setwantsurltome) {
1918
            $SESSION->wantsurl = $FULLME;
1919
        }
1920
        if (!empty($_SERVER['HTTP_REFERER'])) {
1921
            $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
1922
        }
1923
        if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1924
            $loginguest = '?loginguest=true';
1925
        } else {
1926
            $loginguest = '';
1927
        }
1928
        if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
1929
            redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1930
        } else {
1931
            $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1932
            redirect($wwwroot .'/login/index.php');
1933
        }
1934
        exit;
1935
    }
1936

    
1937
/// loginas as redirection if needed
1938
    if ($COURSE->id != SITEID and !empty($USER->realuser)) {
1939
        if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
1940
            if ($USER->loginascontext->instanceid != $COURSE->id) {
1941
                print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
1942
            }
1943
        }
1944
    }
1945

    
1946
/// check whether the user should be changing password (but only if it is REALLY them)
1947
    if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
1948
        $userauth = get_auth_plugin($USER->auth);
1949
        if ($userauth->can_change_password()) {
1950
            $SESSION->wantsurl = $FULLME;
1951
            if ($changeurl = $userauth->change_password_url()) {
1952
                //use plugin custom url
1953
                redirect($changeurl);
1954
            } else {
1955
                //use moodle internal method
1956
                if (empty($CFG->loginhttps)) {
1957
                    redirect($CFG->wwwroot .'/login/change_password.php');
1958
                } else {
1959
                    $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1960
                    redirect($wwwroot .'/login/change_password.php');
1961
                }
1962
            }
1963
        } else {
1964
            print_error('nopasswordchangeforced', 'auth');
1965
        }
1966
    }
1967

    
1968
/// Check that the user account is properly set up
1969
    if (user_not_fully_set_up($USER)) {
1970
        $SESSION->wantsurl = $FULLME;
1971
        redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1972
    }
1973

    
1974
/// Make sure current IP matches the one for this session (if required)
1975
    if (!empty($CFG->tracksessionip)) {
1976
        if ($USER->sessionIP != md5(getremoteaddr())) {
1977
            print_error('sessionipnomatch', 'error');
1978
        }
1979
    }
1980

    
1981
/// Make sure the USER has a sesskey set up.  Used for checking script parameters.
1982
    sesskey();
1983

    
1984
    // Check that the user has agreed to a site policy if there is one
1985
    if (!empty($CFG->sitepolicy)) {
1986
        if (!$USER->policyagreed) {
1987
            $SESSION->wantsurl = $FULLME;
1988
            redirect($CFG->wwwroot .'/user/policy.php');
1989
        }
1990
    }
1991

    
1992
    // Fetch the system context, we are going to use it a lot.
1993
    $sysctx = get_context_instance(CONTEXT_SYSTEM);
1994

    
1995
/// If the site is currently under maintenance, then print a message
1996
    if (!has_capability('moodle/site:config', $sysctx)) {
1997
        if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1998
            print_maintenance_message();
1999
            exit;
2000
        }
2001
    }
2002

    
2003
/// groupmembersonly access control
2004
    if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2005
        if (isguestuser() or !groups_has_membership($cm)) {
2006
            print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
2007
        }
2008
    }
2009

    
2010
    // Fetch the course context, and prefetch its child contexts
2011
    if (!isset($COURSE->context)) {
2012
        if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
2013
            print_error('nocontext');
2014
        }
2015
    }
2016
    if (!empty($cm) && !isset($cm->context)) {
2017
        if ( ! $cm->context = get_context_instance(CONTEXT_MODULE, $cm->id) ) {
2018
            print_error('nocontext');
2019
        }
2020
    }
2021
    if ($COURSE->id == SITEID) {
2022
        /// Eliminate hidden site activities straight away
2023
        if (!empty($cm) && !$cm->visible
2024
            && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2025
            redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
2026
        }
2027
        user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2028
        return;
2029

    
2030
    } else {
2031

    
2032
        /// Check if the user can be in a particular course
2033
        if (empty($USER->access['rsw'][$COURSE->context->path])) {
2034
            //
2035
            // MDL-13900 - If the course or the parent category are hidden
2036
            // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
2037
            //
2038
            if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
2039
                   !has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
2040
                print_header_simple();
2041
                notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2042
            }
2043
        }
2044

    
2045
    /// Non-guests who don't currently have access, check if they can be allowed in as a guest
2046

    
2047
        if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
2048
            if ($COURSE->guest == 1) {
2049
                 // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
2050
                 $USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
2051
            }
2052
        }
2053

    
2054
    /// If the user is a guest then treat them according to the course policy about guests
2055

    
2056
        if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
2057
            if (has_capability('moodle/site:doanything', $sysctx)) {
2058
                // administrators must be able to access any course - even if somebody gives them guest access
2059
                user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2060
                return;
2061
            }
2062

    
2063
            switch ($COURSE->guest) {    /// Check course policy about guest access
2064

    
2065
                case 1:    /// Guests always allowed
2066
                    if (!has_capability('moodle/course:view', $COURSE->context)) {    // Prohibited by capability
2067
                        print_header_simple();
2068
                        notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2069
                    }
2070
                    if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
2071
                        redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
2072
                                 get_string('activityiscurrentlyhidden'));
2073
                    }
2074

    
2075
                    user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2076
                    return;   // User is allowed to see this course
2077

    
2078
                    break;
2079

    
2080
                case 2:    /// Guests allowed with key
2081
                    if (!empty($USER->enrolkey[$COURSE->id])) {   // Set by enrol/manual/enrol.php
2082
                        user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2083
                        return true;
2084
                    }
2085
                    //  otherwise drop through to logic below (--> enrol.php)
2086
                    break;
2087

    
2088
                default:    /// Guests not allowed
2089
                    $strloggedinasguest = get_string('loggedinasguest');
2090
                    print_header_simple('', '',
2091
                            build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
2092
                    if (empty($USER->access['rsw'][$COURSE->context->path])) {  // Normal guest
2093
                        notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2094
                    } else {
2095
                        notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
2096
                        echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
2097
                        print_footer($COURSE);
2098
                        exit;
2099
                    }
2100
                    break;
2101
            }
2102

    
2103
    /// For non-guests, check if they have course view access
2104

    
2105
        } else if (has_capability('moodle/course:view', $COURSE->context)) {
2106
            if (!empty($USER->realuser)) {   // Make sure the REAL person can also access this course
2107
                if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
2108
                    print_header_simple();
2109
                    notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2110
                }
2111
            }
2112

    
2113
        /// Make sure they can read this activity too, if specified
2114

    
2115
            if (!empty($cm) && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2116
                redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
2117
            }
2118
            user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2119
            return;   // User is allowed to see this course
2120

    
2121
        }
2122

    
2123

    
2124
    /// Currently not enrolled in the course, so see if they want to enrol
2125
        $SESSION->wantsurl = $FULLME;
2126
        redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
2127
        die;
2128
    }
2129
}
2130

    
2131

    
2132

    
2133
/**
2134
 * This function just makes sure a user is logged out.
2135
 *
2136
 * @uses $CFG
2137
 * @uses $USER
2138
 */
2139
function require_logout() {
2140

    
2141
    global $USER, $CFG, $SESSION;
2142

    
2143
    if (isloggedin()) {
2144
        add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2145

    
2146
        $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2147
        foreach($authsequence as $authname) {
2148
            $authplugin = get_auth_plugin($authname);
2149
            $authplugin->prelogout_hook();
2150
        }
2151
    }
2152

    
2153
    if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
2154
        // This method is just to try to avoid silly warnings from PHP 4.3.0
2155
        session_unregister("USER");
2156
        session_unregister("SESSION");
2157
    }
2158

    
2159
    // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
2160
    $file = $line = null;
2161
    if (headers_sent($file, $line)) {
2162
        error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
2163
        error_log('Headers were already sent in file: '.$file.' on line '.$line);
2164
    } else {
2165
        if (check_php_version('5.2.0')) {
2166
            setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
2167
        } else {
2168
            setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2169
        }
2170
    }
2171

    
2172
    unset($_SESSION['USER']);
2173
    unset($_SESSION['SESSION']);
2174

    
2175
    unset($SESSION);
2176
    unset($USER);
2177

    
2178
}
2179

    
2180
/**
2181
 * This is a weaker version of {@link require_login()} which only requires login
2182
 * when called from within a course rather than the site page, unless
2183
 * the forcelogin option is turned on.
2184
 *
2185
 * @uses $CFG
2186
 * @param mixed $courseorid The course object or id in question
2187
 * @param bool $autologinguest Allow autologin guests if that is wanted
2188
 * @param object $cm Course activity module if known
2189
 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2190
 *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2191
 *             in order to keep redirects working properly. MDL-14495
2192
 */
2193
function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
2194
    global $CFG;
2195
    if (!empty($CFG->forcelogin)) {
2196
        // login required for both SITE and courses
2197
        require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2198

    
2199
    } else if (!empty($cm) and !$cm->visible) {
2200
        // always login for hidden activities
2201
        require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2202

    
2203
    } else if ((is_object($courseorid) and $courseorid->id == SITEID)
2204
          or (!is_object($courseorid) and $courseorid == SITEID)) {
2205
              //login for SITE not required
2206
        if ($cm and empty($cm->visible)) {
2207
            // hidden activities are not accessible without login
2208
            require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2209
        } else if ($cm and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
2210
            // not-logged-in users do not have any group membership
2211
            require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2212
        } else {
2213
            user_accesstime_log(SITEID);
2214
            return;
2215
        }
2216

    
2217
    } else {
2218
        // course login always required
2219
        require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2220
    }
2221
}
2222

    
2223
/**
2224
 * Require key login. Function terminates with error if key not found or incorrect.
2225
 * @param string $script unique script identifier
2226
 * @param int $instance optional instance id
2227
 */
2228
function require_user_key_login($script, $instance=null) {
2229
    global $nomoodlecookie, $USER, $SESSION, $CFG;
2230

    
2231
    if (empty($nomoodlecookie)) {
2232
        error('Incorrect use of require_key_login() - session cookies must be disabled!');
2233
    }
2234

    
2235
/// extra safety
2236
    @session_write_close();
2237

    
2238
    $keyvalue = required_param('key', PARAM_ALPHANUM);
2239

    
2240
    if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
2241
        error('Incorrect key');
2242
    }
2243

    
2244
    if (!empty($key->validuntil) and $key->validuntil < time()) {
2245
        error('Expired key');
2246
    }
2247

    
2248
    if ($key->iprestriction) {
2249
        $remoteaddr = getremoteaddr();
2250
        if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
2251
            error('Client IP address mismatch');
2252
        }
2253
    }
2254

    
2255
    if (!$user = get_record('user', 'id', $key->userid)) {
2256
        error('Incorrect user record');
2257
    }
2258

    
2259
/// emulate normal session
2260
    $SESSION = new object();
2261
    $USER    = $user;
2262

    
2263
/// note we are not using normal login
2264
    if (!defined('USER_KEY_LOGIN')) {
2265
        define('USER_KEY_LOGIN', true);
2266
    }
2267

    
2268
    load_all_capabilities();
2269

    
2270
/// return isntance id - it might be empty
2271
    return $key->instance;
2272
}
2273

    
2274
/**
2275
 * Creates a new private user access key.
2276
 * @param string $script unique target identifier
2277
 * @param int $userid
2278
 * @param instance $int optional instance id
2279
 * @param string $iprestriction optional ip restricted access
2280
 * @param timestamp $validuntil key valid only until given data
2281
 * @return string access key value
2282
 */
2283
function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2284
    $key = new object();
2285
    $key->script        = $script;
2286
    $key->userid        = $userid;
2287
    $key->instance      = $instance;
2288
    $key->iprestriction = $iprestriction;
2289
    $key->validuntil    = $validuntil;
2290
    $key->timecreated   = time();
2291

    
2292
    $key->value         = md5($userid.'_'.time().random_string(40)); // something long and unique
2293
    while (record_exists('user_private_key', 'value', $key->value)) {
2294
        // must be unique
2295
        $key->value     = md5($userid.'_'.time().random_string(40));
2296
    }
2297

    
2298
    if (!insert_record('user_private_key', $key)) {
2299
        error('Can not insert new key');
2300
    }
2301

    
2302
    return $key->value;
2303
}
2304

    
2305
/**
2306
 * Modify the user table by setting the currently logged in user's
2307
 * last login to now.
2308
 *
2309
 * @uses $USER
2310
 * @return bool
2311
 */
2312
function update_user_login_times() {
2313
    global $USER;
2314

    
2315
    $user = new object();
2316
    $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
2317
    $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
2318

    
2319
    $user->id = $USER->id;
2320

    
2321
    return update_record('user', $user);
2322
}
2323

    
2324
/**
2325
 * Determines if a user has completed setting up their account.
2326
 *
2327
 * @param user $user A {@link $USER} object to test for the existance of a valid name and email
2328
 * @return bool
2329
 */
2330
function user_not_fully_set_up($user) {
2331
    return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
2332
}
2333

    
2334
function over_bounce_threshold($user) {
2335

    
2336
    global $CFG;
2337

    
2338
    if (empty($CFG->handlebounces)) {
2339
        return false;
2340
    }
2341

    
2342
    if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2343
        return false;
2344
    }
2345

    
2346
    // set sensible defaults
2347
    if (empty($CFG->minbounces)) {
2348
        $CFG->minbounces = 10;
2349
    }
2350
    if (empty($CFG->bounceratio)) {
2351
        $CFG->bounceratio = .20;
2352
    }
2353
    $bouncecount = 0;
2354
    $sendcount = 0;
2355
    if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2356
        $bouncecount = $bounce->value;
2357
    }
2358
    if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2359
        $sendcount = $send->value;
2360
    }
2361
    return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
2362
}
2363

    
2364
/**
2365
 * @param $user - object containing an id
2366
 * @param $reset - will reset the count to 0
2367
 */
2368
function set_send_count($user,$reset=false) {
2369

    
2370
    if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2371
        return;
2372
    }
2373

    
2374
    if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2375
        $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2376
        update_record('user_preferences',$pref);
2377
    }
2378
    else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2379
        // make a new one
2380
        $pref->name = 'email_send_count';
2381
        $pref->value = 1;
2382
        $pref->userid = $user->id;
2383
        insert_record('user_preferences',$pref, false);
2384
    }
2385
}
2386

    
2387
/**
2388
* @param $user - object containing an id
2389
 * @param $reset - will reset the count to 0
2390
 */
2391
function set_bounce_count($user,$reset=false) {
2392
    if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2393
        $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2394
        update_record('user_preferences',$pref);
2395
    }
2396
    else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2397
        // make a new one
2398
        $pref->name = 'email_bounce_count';
2399
        $pref->value = 1;
2400
        $pref->userid = $user->id;
2401
        insert_record('user_preferences',$pref, false);
2402
    }
2403
}
2404

    
2405
/**
2406
 * Keeps track of login attempts
2407
 *
2408
 * @uses $SESSION
2409
 */
2410
function update_login_count() {
2411

    
2412
    global $SESSION;
2413

    
2414
    $max_logins = 10;
2415

    
2416
    if (empty($SESSION->logincount)) {
2417
        $SESSION->logincount = 1;
2418
    } else {
2419
        $SESSION->logincount++;
2420
    }
2421

    
2422
    if ($SESSION->logincount > $max_logins) {
2423
        unset($SESSION->wantsurl);
2424
        print_error('errortoomanylogins');
2425
    }
2426
}
2427

    
2428
/**
2429
 * Resets login attempts
2430
 *
2431
 * @uses $SESSION
2432
 */
2433
function reset_login_count() {
2434
    global $SESSION;
2435

    
2436
    $SESSION->logincount = 0;
2437
}
2438

    
2439
function sync_metacourses() {
2440

    
2441
    global $CFG;
2442

    
2443
    if (!$courses = get_records('course', 'metacourse', 1)) {
2444
        return;
2445
    }
2446

    
2447
    foreach ($courses as $course) {
2448
        sync_metacourse($course);
2449
    }
2450
}
2451

    
2452
/**
2453
 * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2454
 *
2455
 * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2456
 */
2457
function sync_metacourse($course) {
2458
    global $CFG;
2459

    
2460
    // Check the course is valid.
2461
    if (!is_object($course)) {
2462
        if (!$course = get_record('course', 'id', $course)) {
2463
            return false; // invalid course id
2464
        }
2465
    }
2466

    
2467
    // Check that we actually have a metacourse.
2468
    if (empty($course->metacourse)) {
2469
        return false;
2470
    }
2471

    
2472
    // Get a list of roles that should not be synced.
2473
    if (!empty($CFG->nonmetacoursesyncroleids)) {
2474
        $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2475
    } else {
2476
        $roleexclusions = '';
2477
    }
2478

    
2479
    // Get the context of the metacourse.
2480
    $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2481

    
2482
    // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2483
    if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2484
        $managers = array_keys($users);
2485
    } else {
2486
        $managers = array();
2487
    }
2488

    
2489
    // Get assignments of a user to a role that exist in a child course, but
2490
    // not in the meta coure. That is, get a list of the assignments that need to be made.
2491
    if (!$assignments = get_records_sql("
2492
            SELECT
2493
                ra.id, ra.roleid, ra.userid, ra.hidden
2494
            FROM
2495
                {$CFG->prefix}role_assignments ra,
2496
                {$CFG->prefix}context con,
2497
                {$CFG->prefix}course_meta cm
2498
            WHERE
2499
                ra.contextid = con.id AND
2500
                con.contextlevel = " . CONTEXT_COURSE . " AND
2501
                con.instanceid = cm.child_course AND
2502
                cm.parent_course = {$course->id} AND
2503
                $roleexclusions
2504
                NOT EXISTS (
2505
                    SELECT 1 FROM
2506
                        {$CFG->prefix}role_assignments ra2
2507
                    WHERE
2508
                        ra2.userid = ra.userid AND
2509
                        ra2.roleid = ra.roleid AND
2510
                        ra2.contextid = {$context->id}
2511
                )
2512
    ")) {
2513
        $assignments = array();
2514
    }
2515

    
2516
    // Get assignments of a user to a role that exist in the meta course, but
2517
    // not in any child courses. That is, get a list of the unassignments that need to be made.
2518
    if (!$unassignments = get_records_sql("
2519
            SELECT
2520
                ra.id, ra.roleid, ra.userid
2521
            FROM
2522
                {$CFG->prefix}role_assignments ra
2523
            WHERE
2524
                ra.contextid = {$context->id} AND
2525
                $roleexclusions
2526
                NOT EXISTS (
2527
                    SELECT 1 FROM
2528
                        {$CFG->prefix}role_assignments ra2,
2529
                        {$CFG->prefix}context con2,
2530
                        {$CFG->prefix}course_meta cm
2531
                    WHERE
2532
                        ra2.userid = ra.userid AND
2533
                        ra2.roleid = ra.roleid AND
2534
                        ra2.contextid = con2.id AND
2535
                        con2.contextlevel = " . CONTEXT_COURSE . " AND
2536
                        con2.instanceid = cm.child_course AND
2537
                        cm.parent_course = {$course->id}
2538
                )
2539
    ")) {
2540
        $unassignments = array();
2541
    }
2542

    
2543
    $success = true;
2544

    
2545
    // Make the unassignments, if they are not managers.
2546
    foreach ($unassignments as $unassignment) {
2547
        if (!in_array($unassignment->userid, $managers)) {
2548
            $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2549
        }
2550
    }
2551

    
2552
    // Make the assignments.
2553
    foreach ($assignments as $assignment) {
2554
        $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id, 0, 0, $assignment->hidden) && $success;
2555
    }
2556

    
2557
    return $success;
2558

    
2559
// TODO: finish timeend and timestart
2560
// maybe we could rely on cron job to do the cleaning from time to time
2561
}
2562

    
2563
/**
2564
 * Adds a record to the metacourse table and calls sync_metacoures
2565
 */
2566
function add_to_metacourse ($metacourseid, $courseid) {
2567

    
2568
    if (!$metacourse = get_record("course","id",$metacourseid)) {
2569
        return false;
2570
    }
2571

    
2572
    if (!$course = get_record("course","id",$courseid)) {
2573
        return false;
2574
    }
2575

    
2576
    if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2577
        $rec = new object();
2578
        $rec->parent_course = $metacourseid;
2579
        $rec->child_course = $courseid;
2580
        if (!insert_record('course_meta',$rec)) {
2581
            return false;
2582
        }
2583
        return sync_metacourse($metacourseid);
2584
    }
2585
    return true;
2586

    
2587
}
2588

    
2589
/**
2590
 * Removes the record from the metacourse table and calls sync_metacourse
2591
 */
2592
function remove_from_metacourse($metacourseid, $courseid) {
2593

    
2594
    if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2595
        return sync_metacourse($metacourseid);
2596
    }
2597
    return false;
2598
}
2599

    
2600

    
2601
/**
2602
 * Determines if a user is currently logged in
2603
 *
2604
 * @uses $USER
2605
 * @return bool
2606
 */
2607
function isloggedin() {
2608
    global $USER;
2609

    
2610
    return (!empty($USER->id));
2611
}
2612

    
2613
/**
2614
 * Determines if a user is logged in as real guest user with username 'guest'.
2615
 * This function is similar to original isguest() in 1.6 and earlier.
2616
 * Current isguest() is deprecated - do not use it anymore.
2617
 *
2618
 * @param $user mixed user object or id, $USER if not specified
2619
 * @return bool true if user is the real guest user, false if not logged in or other user
2620
 */
2621
function isguestuser($user=NULL) {
2622
    global $USER;
2623
    if ($user === NULL) {
2624
        $user = $USER;
2625
    } else if (is_numeric($user)) {
2626
        $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2627
    }
2628

    
2629
    if (empty($user->id)) {
2630
        return false; // not logged in, can not be guest
2631
    }
2632

    
2633
    return ($user->username == 'guest');
2634
}
2635

    
2636
/**
2637
 * Determines if the currently logged in user is in editing mode.
2638
 * Note: originally this function had $userid parameter - it was not usable anyway
2639
 *
2640
 * @uses $USER, $PAGE
2641
 * @return bool
2642
 */
2643
function isediting() {
2644
    global $USER, $PAGE;
2645

    
2646
    if (empty($USER->editing)) {
2647
        return false;
2648
    } elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
2649
        return $PAGE->user_allowed_editing();
2650
    }
2651
    return true;//false;
2652
}
2653

    
2654
/**
2655
 * Determines if the logged in user is currently moving an activity
2656
 *
2657
 * @uses $USER
2658
 * @param int $courseid The id of the course being tested
2659
 * @return bool
2660
 */
2661
function ismoving($courseid) {
2662
    global $USER;
2663

    
2664
    if (!empty($USER->activitycopy)) {
2665
        return ($USER->activitycopycourse == $courseid);
2666
    }
2667
    return false;
2668
}
2669

    
2670
/**
2671
 * Given an object containing firstname and lastname
2672
 * values, this function returns a string with the
2673
 * full name of the person.
2674
 * The result may depend on system settings
2675
 * or language.  'override' will force both names
2676
 * to be used even if system settings specify one.
2677
 *
2678
 * @uses $CFG
2679
 * @uses $SESSION
2680
 * @param object $user A {@link $USER} object to get full name of
2681
 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2682
 */
2683
function fullname($user, $override=false) {
2684

    
2685
    global $CFG, $SESSION;
2686

    
2687
    if (!isset($user->firstname) and !isset($user->lastname)) {
2688
        return '';
2689
    }
2690

    
2691
    if (!$override) {
2692
        if (!empty($CFG->forcefirstname)) {
2693
            $user->firstname = $CFG->forcefirstname;
2694
        }
2695
        if (!empty($CFG->forcelastname)) {
2696
            $user->lastname = $CFG->forcelastname;
2697
        }
2698
    }
2699

    
2700
    if (!empty($SESSION->fullnamedisplay)) {
2701
        $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2702
    }
2703

    
2704
    if ($CFG->fullnamedisplay == 'firstname lastname') {
2705
        return $user->firstname .' '. $user->lastname;
2706

    
2707
    } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2708
        return $user->lastname .' '. $user->firstname;
2709

    
2710
    } else if ($CFG->fullnamedisplay == 'firstname') {
2711
        if ($override) {
2712
            return get_string('fullnamedisplay', '', $user);
2713
        } else {
2714
            return $user->firstname;
2715
        }
2716
    }
2717

    
2718
    return get_string('fullnamedisplay', '', $user);
2719
}
2720

    
2721
/**
2722
 * Sets a moodle cookie with an encrypted string
2723
 *
2724
 * @uses $CFG
2725
 * @uses DAYSECS
2726
 * @uses HOURSECS
2727
 * @param string $thing The string to encrypt and place in a cookie
2728
 */
2729
function set_moodle_cookie($thing) {
2730
    global $CFG;
2731

    
2732
    if ($thing == 'guest') {  // Ignore guest account
2733
        return;
2734
    }
2735

    
2736
    $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2737

    
2738
    $days = 60;
2739
    $seconds = DAYSECS*$days;
2740

    
2741
    setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2742
    setCookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2743
}
2744

    
2745
/**
2746
 * Gets a moodle cookie with an encrypted string
2747
 *
2748
 * @uses $CFG
2749
 * @return string
2750
 */
2751
function get_moodle_cookie() {
2752
    global $CFG;
2753

    
2754
    $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2755

    
2756
    if (empty($_COOKIE[$cookiename])) {
2757
        return '';
2758
    } else {
2759
        $thing = rc4decrypt($_COOKIE[$cookiename]);
2760
        return ($thing == 'guest') ? '': $thing;  // Ignore guest account
2761
    }
2762
}
2763

    
2764
/**
2765
 * Returns whether a given authentication plugin exists.
2766
 *
2767
 * @uses $CFG
2768
 * @param string $auth Form of authentication to check for. Defaults to the
2769
 *        global setting in {@link $CFG}.
2770
 * @return boolean Whether the plugin is available.
2771
 */
2772
function exists_auth_plugin($auth) {
2773
    global $CFG;
2774

    
2775
    if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2776
        return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2777
    }
2778
    return false;
2779
}
2780

    
2781
/**
2782
 * Checks if a given plugin is in the list of enabled authentication plugins.
2783
 *
2784
 * @param string $auth Authentication plugin.
2785
 * @return boolean Whether the plugin is enabled.
2786
 */
2787
function is_enabled_auth($auth) {
2788
    if (empty($auth)) {
2789
        return false;
2790
    }
2791

    
2792
    $enabled = get_enabled_auth_plugins();
2793

    
2794
    return in_array($auth, $enabled);
2795
}
2796

    
2797
/**
2798
 * Returns an authentication plugin instance.
2799
 *
2800
 * @uses $CFG
2801
 * @param string $auth name of authentication plugin
2802
 * @return object An instance of the required authentication plugin.
2803
 */
2804
function get_auth_plugin($auth) {
2805
    global $CFG;
2806

    
2807
    // check the plugin exists first
2808
    if (! exists_auth_plugin($auth)) {
2809
        error("Authentication plugin '$auth' not found.");
2810
    }
2811

    
2812
    // return auth plugin instance
2813
    require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2814
    $class = "auth_plugin_$auth";
2815
    return new $class;
2816
}
2817

    
2818
/**
2819
 * Returns array of active auth plugins.
2820
 *
2821
 * @param bool $fix fix $CFG->auth if needed
2822
 * @return array
2823
 */
2824
function get_enabled_auth_plugins($fix=false) {
2825
    global $CFG;
2826

    
2827
    $default = array('manual', 'nologin');
2828

    
2829
    if (empty($CFG->auth)) {
2830
        $auths = array();
2831
    } else {
2832
        $auths = explode(',', $CFG->auth);
2833
    }
2834

    
2835
    if ($fix) {
2836
        $auths = array_unique($auths);
2837
        foreach($auths as $k=>$authname) {
2838
            if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
2839
                unset($auths[$k]);
2840
            }
2841
        }
2842
        $newconfig = implode(',', $auths);
2843
        if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
2844
            set_config('auth', $newconfig);
2845
        }
2846
    }
2847

    
2848
    return (array_merge($default, $auths));
2849
}
2850

    
2851
/**
2852
 * Returns true if an internal authentication method is being used.
2853
 * if method not specified then, global default is assumed
2854
 *
2855
 * @uses $CFG
2856
 * @param string $auth Form of authentication required
2857
 * @return bool
2858
 */
2859
function is_internal_auth($auth) {
2860
    $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2861
    return $authplugin->is_internal();
2862
}
2863

    
2864
/**
2865
 * Returns true if the user is a 'restored' one
2866
 *
2867
 * Used in the login process to inform the user
2868
 * and allow him/her to reset the password
2869
 *
2870
 * @uses $CFG
2871
 * @param string $username username to be checked
2872
 * @return bool
2873
 */
2874
function is_restored_user($username) {
2875
    global $CFG;
2876

    
2877
    return record_exists('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, 'password', 'restored');
2878
}
2879

    
2880
/**
2881
 * Returns an array of user fields
2882
 *
2883
 * @uses $CFG
2884
 * @uses $db
2885
 * @return array User field/column names
2886
 */
2887
function get_user_fieldnames() {
2888

    
2889
    global $CFG, $db;
2890

    
2891
    $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2892
    unset($fieldarray['ID']);
2893

    
2894
    return $fieldarray;
2895
}
2896

    
2897
/**
2898
 * Creates the default "guest" user. Used both from
2899
 * admin/index.php and login/index.php
2900
 * @return mixed user object created or boolean false if the creation has failed
2901
 */
2902
function create_guest_record() {
2903

    
2904
    global $CFG;
2905

    
2906
    $guest = new stdClass();
2907
    $guest->auth        = 'manual';
2908
    $guest->username    = 'guest';
2909
    $guest->password    = hash_internal_user_password('guest');
2910
    $guest->firstname   = addslashes(get_string('guestuser'));
2911
    $guest->lastname    = ' ';
2912
    $guest->email       = 'root@localhost';
2913
    $guest->description = addslashes(get_string('guestuserinfo'));
2914
    $guest->mnethostid  = $CFG->mnet_localhost_id;
2915
    $guest->confirmed   = 1;
2916
    $guest->lang        = $CFG->lang;
2917
    $guest->timemodified= time();
2918

    
2919
    if (! $guest->id = insert_record("user", $guest)) {
2920
        return false;
2921
    }
2922

    
2923
    return $guest;
2924
}
2925

    
2926
/**
2927
 * Creates a bare-bones user record
2928
 *
2929
 * @uses $CFG
2930
 * @param string $username New user's username to add to record
2931
 * @param string $password New user's password to add to record
2932
 * @param string $auth Form of authentication required
2933
 * @return object A {@link $USER} object
2934
 * @todo Outline auth types and provide code example
2935
 */
2936
function create_user_record($username, $password, $auth='manual') {
2937
    global $CFG;
2938

    
2939
    //just in case check text case
2940
    $username = trim(moodle_strtolower($username));
2941

    
2942
    $authplugin = get_auth_plugin($auth);
2943

    
2944
    if ($newinfo = $authplugin->get_userinfo($username)) {
2945
        $newinfo = truncate_userinfo($newinfo);
2946
        foreach ($newinfo as $key => $value){
2947
            $newuser->$key = addslashes($value);
2948
        }
2949
    }
2950

    
2951
    if (!empty($newuser->email)) {
2952
        if (email_is_not_allowed($newuser->email)) {
2953
            unset($newuser->email);
2954
        }
2955
    }
2956

    
2957
    if (!isset($newuser->city)) {
2958
        $newuser->city = '';
2959
    }
2960

    
2961
    $newuser->auth = $auth;
2962
    $newuser->username = $username;
2963

    
2964
    // fix for MDL-8480
2965
    // user CFG lang for user if $newuser->lang is empty
2966
    // or $user->lang is not an installed language
2967
    $sitelangs = array_keys(get_list_of_languages());
2968
    if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
2969
        $newuser -> lang = $CFG->lang;
2970
    }
2971
    $newuser->confirmed = 1;
2972
    $newuser->lastip = getremoteaddr();
2973
    $newuser->timemodified = time();
2974
    $newuser->mnethostid = $CFG->mnet_localhost_id;
2975

    
2976
    if (insert_record('user', $newuser)) {
2977
        $user = get_complete_user_data('username', $newuser->username);
2978
        if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
2979
            set_user_preference('auth_forcepasswordchange', 1, $user->id);
2980
        }
2981
        update_internal_user_password($user, $password);
2982
        return $user;
2983
    }
2984
    return false;
2985
}
2986

    
2987
/**
2988
 * Will update a local user record from an external source
2989
 *
2990
 * @uses $CFG
2991
 * @param string $username New user's username to add to record
2992
 * @return user A {@link $USER} object
2993
 */
2994
function update_user_record($username, $authplugin) {
2995
    $username = trim(moodle_strtolower($username)); /// just in case check text case
2996

    
2997
    $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2998
    $userauth = get_auth_plugin($oldinfo->auth);
2999

    
3000
    if ($newinfo = $userauth->get_userinfo($username)) {
3001
        $newinfo = truncate_userinfo($newinfo);
3002
        foreach ($newinfo as $key => $value){
3003
            if ($key === 'username') {
3004
                // 'username' is not a mapped updateable/lockable field, so skip it.
3005
                continue;
3006
            }
3007
            $confval = $userauth->config->{'field_updatelocal_' . $key};
3008
            $lockval = $userauth->config->{'field_lock_' . $key};
3009
            if (empty($confval) || empty($lockval)) {
3010
                continue;
3011
            }
3012
            if ($confval === 'onlogin') {
3013
                $value = addslashes($value);
3014
                // MDL-4207 Don't overwrite modified user profile values with
3015
                // empty LDAP values when 'unlocked if empty' is set. The purpose
3016
                // of the setting 'unlocked if empty' is to allow the user to fill
3017
                // in a value for the selected field _if LDAP is giving
3018
                // nothing_ for this field. Thus it makes sense to let this value
3019
                // stand in until LDAP is giving a value for this field.
3020
                if (!(empty($value) && $lockval === 'unlockedifempty')) {
3021
                    set_field('user', $key, $value, 'username', $username)
3022
                        || error_log("Error updating $key for $username");
3023
                }
3024
            }
3025
        }
3026
    }
3027

    
3028
    return get_complete_user_data('username', $username);
3029
}
3030

    
3031
function truncate_userinfo($info) {
3032
/// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3033
/// which may have large fields
3034

    
3035
    // define the limits
3036
    $limit = array(
3037
                    'username'    => 100,
3038
                    'idnumber'    => 255,
3039
                    'firstname'   => 100,
3040
                    'lastname'    => 100,
3041
                    'email'       => 100,
3042
                    'icq'         =>  15,
3043
                    'phone1'      =>  20,
3044
                    'phone2'      =>  20,
3045
                    'institution' =>  40,
3046
                    'department'  =>  30,
3047
                    'address'     =>  70,
3048
                    'city'        =>  20,
3049
                    'country'     =>   2,
3050
                    'url'         => 255,
3051
                    );
3052

    
3053
    // apply where needed
3054
    foreach (array_keys($info) as $key) {
3055
        if (!empty($limit[$key])) {
3056
            $info[$key] = trim(substr($info[$key],0, $limit[$key]));
3057
        }
3058
    }
3059

    
3060
    return $info;
3061
}
3062

    
3063
/**
3064
 * Marks user deleted in internal user database and notifies the auth plugin.
3065
 * Also unenrols user from all roles and does other cleanup.
3066
 * @param object $user       Userobject before delete    (without system magic quotes)
3067
 * @return boolean success
3068
 */
3069
function delete_user($user) {
3070
    global $CFG;
3071
    require_once($CFG->libdir.'/grouplib.php');
3072
    require_once($CFG->libdir.'/gradelib.php');
3073
    require_once($CFG->dirroot.'/message/lib.php');
3074

    
3075
    begin_sql();
3076

    
3077
    // delete all grades - backup is kept in grade_grades_history table
3078
    if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
3079
        foreach ($grades as $grade) {
3080
            $grade->delete('userdelete');
3081
        }
3082
    }
3083

    
3084
    //move unread messages from this user to read
3085
    message_move_userfrom_unread2read($user->id);
3086

    
3087
    // remove from all groups
3088
    delete_records('groups_members', 'userid', $user->id);
3089

    
3090
    // unenrol from all roles in all contexts
3091
    role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
3092

    
3093
    // now do a final accesslib cleanup - removes all role assingments in user context and context itself
3094
    delete_context(CONTEXT_USER, $user->id);
3095

    
3096
    require_once($CFG->dirroot.'/tag/lib.php');
3097
    tag_set('user', $user->id, array());
3098

    
3099
    // workaround for bulk deletes of users with the same email address
3100
    $delname = addslashes("$user->email.".time());
3101
    while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
3102
        $delname++;
3103
    }
3104

    
3105
    // mark internal user record as "deleted"
3106
    $updateuser = new object();
3107
    $updateuser->id           = $user->id;
3108
    $updateuser->deleted      = 1;
3109
    $updateuser->username     = $delname;            // Remember it just in case
3110
    $updateuser->email        = md5($user->username);// Store hash of username, useful importing/restoring users
3111
    $updateuser->idnumber     = '';                  // Clear this field to free it up
3112
    $updateuser->timemodified = time();
3113

    
3114
    if (update_record('user', $updateuser)) {
3115
        commit_sql();
3116
        // notify auth plugin - do not block the delete even when plugin fails
3117
        $authplugin = get_auth_plugin($user->auth);
3118
        $authplugin->user_delete($user);
3119

    
3120
        events_trigger('user_deleted', $user);
3121
        return true;
3122

    
3123
    } else {
3124
        rollback_sql();
3125
        return false;
3126
    }
3127
}
3128

    
3129
/**
3130
 * Retrieve the guest user object
3131
 *
3132
 * @uses $CFG
3133
 * @return user A {@link $USER} object
3134
 */
3135
function guest_user() {
3136
    global $CFG;
3137

    
3138
    if ($newuser = get_record('user', 'username', 'guest', 'mnethostid',  $CFG->mnet_localhost_id)) {
3139
        $newuser->confirmed = 1;
3140
        $newuser->lang = $CFG->lang;
3141
        $newuser->lastip = getremoteaddr();
3142
    }
3143

    
3144
    return $newuser;
3145
}
3146

    
3147
/**
3148
 * Given a username and password, this function looks them
3149
 * up using the currently selected authentication mechanism,
3150
 * and if the authentication is successful, it returns a
3151
 * valid $user object from the 'user' table.
3152
 *
3153
 * Uses auth_ functions from the currently active auth module
3154
 *
3155
 * After authenticate_user_login() returns success, you will need to
3156
 * log that the user has logged in, and call complete_user_login() to set
3157
 * the session up.
3158
 *
3159
 * @uses $CFG
3160
 * @param string $username  User's username (with system magic quotes)
3161
 * @param string $password  User's password (with system magic quotes)
3162
 * @return user|flase A {@link $USER} object or false if error
3163
 */
3164
function authenticate_user_login($username, $password) {
3165

    
3166
    global $CFG;
3167

    
3168
    $authsenabled = get_enabled_auth_plugins();
3169

    
3170
    if ($user = get_complete_user_data('username', $username)) {
3171
        $auth = empty($user->auth) ? 'manual' : $user->auth;  // use manual if auth not set
3172
        if ($auth=='nologin' or !is_enabled_auth($auth)) {
3173
            add_to_log(0, 'login', 'error', 'index.php', $username);
3174
            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Disabled Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
3175
            return false;
3176
        }
3177
        $auths = array($auth);
3178

    
3179
    } else {
3180
        // check if there's a deleted record (cheaply)
3181
        if (get_field('user', 'id', 'username', $username, 'deleted', 1, '')) {
3182
            error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Deleted Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
3183
            return false;
3184
        }
3185

    
3186
        $auths = $authsenabled;
3187
        $user = new object();
3188
        $user->id = 0;     // User does not exist
3189
    }
3190

    
3191
    foreach ($auths as $auth) {
3192
        $authplugin = get_auth_plugin($auth);
3193

    
3194
        // on auth fail fall through to the next plugin
3195
        if (!$authplugin->user_login($username, $password)) {
3196
            continue;
3197
        }
3198

    
3199
        // successful authentication
3200
        if ($user->id) {                          // User already exists in database
3201
            if (empty($user->auth)) {             // For some reason auth isn't set yet
3202
                set_field('user', 'auth', $auth, 'username', $username);
3203
                $user->auth = $auth;
3204
            }
3205
            if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
3206
                set_field('user','firstaccess', $user->timemodified, 'id', $user->id);
3207
                $user->firstaccess = $user->timemodified;
3208
            }
3209

    
3210
            update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3211

    
3212
            if (!$authplugin->is_internal()) {            // update user record from external DB
3213
                $user = update_user_record($username, get_auth_plugin($user->auth));
3214
            }
3215
        } else {
3216
            // if user not found, create him
3217
            $user = create_user_record($username, $password, $auth);
3218
        }
3219

    
3220
        $authplugin->sync_roles($user);
3221

    
3222
        foreach ($authsenabled as $hau) {
3223
            $hauth = get_auth_plugin($hau);
3224
            $hauth->user_authenticated_hook($user, $username, $password);
3225
        }
3226

    
3227
    /// Log in to a second system if necessary
3228
    /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
3229
        if (!empty($CFG->sso)) {
3230
            include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
3231
            if (function_exists('sso_user_login')) {
3232
                if (!sso_user_login($username, $password)) {   // Perform the signon process
3233
                    notify('Second sign-on failed');
3234
                }
3235
            }
3236
        }
3237

    
3238
        if ($user->id===0) {
3239
            return false;
3240
        }
3241
        return $user;
3242
    }
3243

    
3244
    // failed if all the plugins have failed
3245
    add_to_log(0, 'login', 'error', 'index.php', $username);
3246
    if (debugging('', DEBUG_ALL)) {
3247
        error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Failed Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
3248
    }
3249
    return false;
3250
}
3251

    
3252
/**
3253
 * Call to complete the user login process after authenticate_user_login()
3254
 * has succeeded. It will setup the $USER variable and other required bits
3255
 * and pieces.
3256
 *
3257
 * NOTE:
3258
 * - It will NOT log anything -- up to the caller to decide what to log.
3259
 *
3260
 *
3261
 *
3262
 * @uses $CFG, $USER
3263
 * @param string $user obj
3264
 * @return user|flase A {@link $USER} object or false if error
3265
 */
3266
function complete_user_login($user) {
3267
    global $CFG, $USER;
3268

    
3269
    $USER = $user; // this is required because we need to access preferences here!
3270

    
3271
    if (!empty($CFG->regenloginsession)) {
3272
        // please note this setting may break some auth plugins
3273
        session_regenerate_id();
3274
    }
3275

    
3276
    reload_user_preferences();
3277

    
3278
    update_user_login_times();
3279
    if (empty($CFG->nolastloggedin)) {
3280
        set_moodle_cookie($USER->username);
3281
    } else {
3282
        // do not store last logged in user in cookie
3283
        // auth plugins can temporarily override this from loginpage_hook()
3284
        // do not save $CFG->nolastloggedin in database!
3285
        set_moodle_cookie('nobody');
3286
    }
3287
    set_login_session_preferences();
3288

    
3289
    // Call enrolment plugins
3290
    check_enrolment_plugins($user);
3291

    
3292
    /// This is what lets the user do anything on the site :-)
3293
    load_all_capabilities();
3294

    
3295
    /// Select password change url
3296
    $userauth = get_auth_plugin($USER->auth);
3297

    
3298
    /// check whether the user should be changing password
3299
    if (get_user_preferences('auth_forcepasswordchange', false)){
3300
        if ($userauth->can_change_password()) {
3301
            if ($changeurl = $userauth->change_password_url()) {
3302
                redirect($changeurl);
3303
            } else {
3304
                redirect($CFG->httpswwwroot.'/login/change_password.php');
3305
            }
3306
        } else {
3307
            print_error('nopasswordchangeforced', 'auth');
3308
        }
3309
    }
3310
    return $USER;
3311
}
3312

    
3313
/**
3314
 * Compare password against hash stored in internal user table.
3315
 * If necessary it also updates the stored hash to new format.
3316
 *
3317
 * @param object user
3318
 * @param string plain text password
3319
 * @return bool is password valid?
3320
 */
3321
function validate_internal_user_password(&$user, $password) {
3322
    global $CFG;
3323

    
3324
    if (!isset($CFG->passwordsaltmain)) {
3325
        $CFG->passwordsaltmain = '';
3326
    }
3327

    
3328
    $validated = false;
3329

    
3330
    // get password original encoding in case it was not updated to unicode yet
3331
    $textlib = textlib_get_instance();
3332
    $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
3333

    
3334
    if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
3335
        or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
3336
        $validated = true;
3337
    } else {
3338
        for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
3339
            $alt = 'passwordsaltalt'.$i;
3340
            if (!empty($CFG->$alt)) {
3341
                if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
3342
                    $validated = true;
3343
                    break;
3344
                }
3345
            }
3346
        }
3347
    }
3348

    
3349
    if ($validated) {
3350
        // force update of password hash using latest main password salt and encoding if needed
3351
        update_internal_user_password($user, $password);
3352
    }
3353

    
3354
    return $validated;
3355
}
3356

    
3357
/**
3358
 * Calculate hashed value from password using current hash mechanism.
3359
 *
3360
 * @param string password
3361
 * @return string password hash
3362
 */
3363
function hash_internal_user_password($password) {
3364
    global $CFG;
3365

    
3366
    if (isset($CFG->passwordsaltmain)) {
3367
        return md5($password.$CFG->passwordsaltmain);
3368
    } else {
3369
        return md5($password);
3370
    }
3371
}
3372

    
3373
/**
3374
 * Update pssword hash in user object.
3375
 *
3376
 * @param object user
3377
 * @param string plain text password
3378
 * @param bool store changes also in db, default true
3379
 * @return true if hash changed
3380
 */
3381
function update_internal_user_password(&$user, $password) {
3382
    global $CFG;
3383

    
3384
    $authplugin = get_auth_plugin($user->auth);
3385
    if ($authplugin->prevent_local_passwords()) {
3386
        $hashedpassword = 'not cached';
3387
    } else {
3388
        $hashedpassword = hash_internal_user_password($password);
3389
    }
3390

    
3391
    return set_field('user', 'password',  $hashedpassword, 'id', $user->id);
3392
}
3393

    
3394
/**
3395
 * Get a complete user record, which includes all the info
3396
 * in the user record
3397
 * Intended for setting as $USER session variable
3398
 *
3399
 * @uses $CFG
3400
 * @uses SITEID
3401
 * @param string $field The user field to be checked for a given value.
3402
 * @param string $value The value to match for $field.
3403
 * @return user A {@link $USER} object.
3404
 */
3405
function get_complete_user_data($field, $value, $mnethostid=null) {
3406

    
3407
    global $CFG;
3408

    
3409
    if (!$field || !$value) {
3410
        return false;
3411
    }
3412

    
3413
/// Build the WHERE clause for an SQL query
3414

    
3415
    $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
3416

    
3417
    // If we are loading user data based on anything other than id,
3418
    // we must also restrict our search based on mnet host.
3419
    if ($field != 'id') {
3420
        if (empty($mnethostid)) {
3421
            // if empty, we restrict to local users
3422
            $mnethostid = $CFG->mnet_localhost_id;
3423
        }
3424
    }
3425
    if (!empty($mnethostid)) {
3426
        $mnethostid = (int)$mnethostid;
3427
        $constraints .= ' AND mnethostid = ' . $mnethostid;
3428
    }
3429

    
3430
/// Get all the basic user data
3431

    
3432
    if (! $user = get_record_select('user', $constraints)) {
3433
        return false;
3434
    }
3435

    
3436
/// Get various settings and preferences
3437

    
3438
    if ($displays = get_records('course_display', 'userid', $user->id)) {
3439
        foreach ($displays as $display) {
3440
            $user->display[$display->course] = $display->display;
3441
        }
3442
    }
3443

    
3444
    $user->preference = get_user_preferences(null, null, $user->id);
3445

    
3446
    $user->lastcourseaccess    = array(); // during last session
3447
    $user->currentcourseaccess = array(); // during current session
3448
    if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
3449
        foreach ($lastaccesses as $lastaccess) {
3450
            $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
3451
        }
3452
    }
3453

    
3454
    $sql = "SELECT g.id, g.courseid
3455
              FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
3456
             WHERE gm.groupid=g.id AND gm.userid={$user->id}";
3457

    
3458
    // this is a special hack to speedup calendar display
3459
    $user->groupmember = array();
3460
    if ($groups = get_records_sql($sql)) {
3461
        foreach ($groups as $group) {
3462
            if (!array_key_exists($group->courseid, $user->groupmember)) {
3463
                $user->groupmember[$group->courseid] = array();
3464
            }
3465
            $user->groupmember[$group->courseid][$group->id] = $group->id;
3466
        }
3467
    }
3468

    
3469
/// Add the custom profile fields to the user record
3470
    include_once($CFG->dirroot.'/user/profile/lib.php');
3471
    $customfields = (array)profile_user_record($user->id);
3472
    foreach ($customfields as $cname=>$cvalue) {
3473
        if (!isset($user->$cname)) { // Don't overwrite any standard fields
3474
            $user->$cname = $cvalue;
3475
        }
3476
    }
3477

    
3478
/// Rewrite some variables if necessary
3479
    if (!empty($user->description)) {
3480
        $user->description = true;   // No need to cart all of it around
3481
    }
3482
    if ($user->username == 'guest') {
3483
        $user->lang       = $CFG->lang;               // Guest language always same as site
3484
        $user->firstname  = get_string('guestuser');  // Name always in current language
3485
        $user->lastname   = ' ';
3486
    }
3487

    
3488
    if (isset($_SERVER['REMOTE_ADDR'])) {
3489
        $user->sesskey  = random_string(10);
3490
        $user->sessionIP = md5(getremoteaddr());   // Store the current IP in the session
3491
    }
3492

    
3493
    return $user;
3494
}
3495

    
3496
/**
3497
 * @uses $CFG
3498
 * @param string $password the password to be checked agains the password policy
3499
 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3500
 * @return bool true if the password is valid according to the policy. false otherwise.
3501
 */
3502
function check_password_policy($password, &$errmsg) {
3503
    global $CFG;
3504

    
3505
    if (empty($CFG->passwordpolicy)) {
3506
        return true;
3507
    }
3508

    
3509
    $textlib = textlib_get_instance();
3510
    $errmsg = '';
3511
    if ($textlib->strlen($password) < $CFG->minpasswordlength) {
3512
        $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
3513

    
3514
    }
3515
    if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
3516
        $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
3517

    
3518
    }
3519
    if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
3520
        $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
3521

    
3522
    }
3523
    if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
3524
        $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
3525

    
3526
    }
3527
    if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
3528
        $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
3529
    }
3530

    
3531
    if ($errmsg == '') {
3532
        return true;
3533
    } else {
3534
        return false;
3535
    }
3536
}
3537

    
3538

    
3539
/**
3540
 * When logging in, this function is run to set certain preferences
3541
 * for the current SESSION
3542
 */
3543
function set_login_session_preferences() {
3544
    global $SESSION, $CFG;
3545

    
3546
    $SESSION->justloggedin = true;
3547

    
3548
    unset($SESSION->lang);
3549

    
3550
    // Restore the calendar filters, if saved
3551
    if (intval(get_user_preferences('calendar_persistflt', 0))) {
3552
        include_once($CFG->dirroot.'/calendar/lib.php');
3553
        calendar_set_filters_status(get_user_preferences('calendar_savedflt', 0xff));
3554
    }
3555
}
3556

    
3557

    
3558
/**
3559
 * Delete a course, including all related data from the database,
3560
 * and any associated files from the moodledata folder.
3561
 *
3562
 * @param mixed $courseorid The id of the course or course object to delete.
3563
 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3564
 * @return bool true if all the removals succeeded. false if there were any failures. If this
3565
 *             method returns false, some of the removals will probably have succeeded, and others
3566
 *             failed, but you have no way of knowing which.
3567
 */
3568
function delete_course($courseorid, $showfeedback = true) {
3569
    global $CFG;
3570
    $result = true;
3571

    
3572
    if (is_object($courseorid)) {
3573
        $courseid = $courseorid->id;
3574
        $course   = $courseorid;
3575
    } else {
3576
        $courseid = $courseorid;
3577
        if (!$course = get_record('course', 'id', $courseid)) {
3578
            return false;
3579
        }
3580
    }
3581

    
3582
    // frontpage course can not be deleted!!
3583
    if ($courseid == SITEID) {
3584
        return false;
3585
    }
3586

    
3587
    if (!remove_course_contents($courseid, $showfeedback)) {
3588
        if ($showfeedback) {
3589
            notify("An error occurred while deleting some of the course contents.");
3590
        }
3591
        $result = false;
3592
    }
3593

    
3594
    if (!delete_records("course", "id", $courseid)) {
3595
        if ($showfeedback) {
3596
            notify("An error occurred while deleting the main course record.");
3597
        }
3598
        $result = false;
3599
    }
3600

    
3601
/// Delete all roles and overiddes in the course context
3602
    if (!delete_context(CONTEXT_COURSE, $courseid)) {
3603
        if ($showfeedback) {
3604
            notify("An error occurred while deleting the main course context.");
3605
        }
3606
        $result = false;
3607
    }
3608

    
3609
    if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3610
        if ($showfeedback) {
3611
            notify("An error occurred while deleting the course files.");
3612
        }
3613
        $result = false;
3614
    }
3615

    
3616
    if ($result) {
3617
        //trigger events
3618
        events_trigger('course_deleted', $course);
3619
    }
3620

    
3621
    return $result;
3622
}
3623

    
3624
/**
3625
 * Clear a course out completely, deleting all content
3626
 * but don't delete the course itself
3627
 *
3628
 * @uses $CFG
3629
 * @param int $courseid The id of the course that is being deleted
3630
 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3631
 * @return bool true if all the removals succeeded. false if there were any failures. If this
3632
 *             method returns false, some of the removals will probably have succeeded, and others
3633
 *             failed, but you have no way of knowing which.
3634
 */
3635
function remove_course_contents($courseid, $showfeedback=true) {
3636

    
3637
    global $CFG;
3638
    require_once($CFG->libdir.'/questionlib.php');
3639
    require_once($CFG->libdir.'/gradelib.php');
3640

    
3641
    $result = true;
3642

    
3643
    if (! $course = get_record('course', 'id', $courseid)) {
3644
        error('Course ID was incorrect (can\'t find it)');
3645
    }
3646

    
3647
    $strdeleted = get_string('deleted');
3648

    
3649
/// Clean up course formats (iterate through all formats in the even the course format was ever changed)
3650
    $formats = get_list_of_plugins('course/format');
3651
    foreach ($formats as $format) {
3652
        $formatdelete = $format.'_course_format_delete_course';
3653
        $formatlib    = "$CFG->dirroot/course/format/$format/lib.php";
3654
        if (file_exists($formatlib)) {
3655
            include_once($formatlib);
3656
            if (function_exists($formatdelete)) {
3657
                if ($showfeedback) {
3658
                    notify($strdeleted.' '.$format);
3659
                }
3660
                $formatdelete($course->id);
3661
            }
3662
        }
3663
    }
3664

    
3665
/// Delete every instance of every module
3666

    
3667
    if ($allmods = get_records('modules') ) {
3668
        foreach ($allmods as $mod) {
3669
            $modname = $mod->name;
3670
            $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3671
            $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance
3672
            $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon)
3673
            $count=0;
3674
            if (file_exists($modfile)) {
3675
                include_once($modfile);
3676
                if (function_exists($moddelete)) {
3677
                    if ($instances = get_records($modname, 'course', $course->id)) {
3678
                        foreach ($instances as $instance) {
3679
                            if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
3680
                                /// Delete activity context questions and question categories
3681
                                question_delete_activity($cm,  $showfeedback);
3682
                            }
3683
                            if ($moddelete($instance->id)) {
3684
                                $count++;
3685

    
3686
                            } else {
3687
                                notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
3688
                                $result = false;
3689
                            }
3690
                            if ($cm) {
3691
                                // delete cm and its context in correct order
3692
                                delete_records('course_modules', 'id', $cm->id);
3693
                                delete_context(CONTEXT_MODULE, $cm->id);
3694
                            }
3695
                        }
3696
                    }
3697
                } else {
3698
                    notify('Function '.$moddelete.'() doesn\'t exist!');
3699
                    $result = false;
3700
                }
3701

    
3702
                if (function_exists($moddeletecourse)) {
3703
                    $moddeletecourse($course, $showfeedback);
3704
                }
3705
            }
3706
            if ($showfeedback) {
3707
                notify($strdeleted .' '. $count .' x '. $modname);
3708
            }
3709
        }
3710
    } else {
3711
        error('No modules are installed!');
3712
    }
3713

    
3714
/// Give local code a chance to delete its references to this course.
3715
    require_once($CFG->libdir.'/locallib.php');
3716
    notify_local_delete_course($courseid, $showfeedback);
3717

    
3718
/// Delete course blocks
3719

    
3720
    if ($blocks = get_records_sql("SELECT *
3721
                                   FROM {$CFG->prefix}block_instance
3722
                                   WHERE pagetype = '".PAGE_COURSE_VIEW."'
3723
                                   AND pageid = $course->id")) {
3724
        if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3725
            if ($showfeedback) {
3726
                notify($strdeleted .' block_instance');
3727
            }
3728

    
3729
            require_once($CFG->libdir.'/blocklib.php');
3730
            foreach ($blocks as $block) {  /// Delete any associated contexts for this block
3731

    
3732
                delete_context(CONTEXT_BLOCK, $block->id);
3733

    
3734
                // fix for MDL-7164
3735
                // Get the block object and call instance_delete()
3736
                if (!$record = blocks_get_record($block->blockid)) {
3737
                    $result = false;
3738
                    continue;
3739
                }
3740
                if (!$obj = block_instance($record->name, $block)) {
3741
                    $result = false;
3742
                    continue;
3743
                }
3744
                // Return value ignored, in core mods this does not do anything, but just in case
3745
                // third party blocks might have stuff to clean up
3746
                // we execute this anyway
3747
                $obj->instance_delete();
3748

    
3749
            }
3750
        } else {
3751
            $result = false;
3752
        }
3753
    }
3754

    
3755
/// Delete any groups, removing members and grouping/course links first.
3756
    require_once($CFG->dirroot.'/group/lib.php');
3757
    groups_delete_groupings($courseid, $showfeedback);
3758
    groups_delete_groups($courseid, $showfeedback);
3759

    
3760
/// Delete all related records in other tables that may have a courseid
3761
/// This array stores the tables that need to be cleared, as
3762
/// table_name => column_name that contains the course id.
3763

    
3764
    $tablestoclear = array(
3765
        'event' => 'courseid', // Delete events
3766
        'log' => 'course', // Delete logs
3767
        'course_sections' => 'course', // Delete any course stuff
3768
        'course_modules' => 'course',
3769
        'backup_courses' => 'courseid', // Delete scheduled backup stuff
3770
        'user_lastaccess' => 'courseid',
3771
        'backup_log' => 'courseid'
3772
    );
3773
    foreach ($tablestoclear as $table => $col) {
3774
        if (delete_records($table, $col, $course->id)) {
3775
            if ($showfeedback) {
3776
                notify($strdeleted . ' ' . $table);
3777
            }
3778
        } else {
3779
            $result = false;
3780
        }
3781
    }
3782

    
3783

    
3784
/// Clean up metacourse stuff
3785

    
3786
    if ($course->metacourse) {
3787
        delete_records("course_meta","parent_course",$course->id);
3788
        sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3789
        if ($showfeedback) {
3790
            notify("$strdeleted course_meta");
3791
        }
3792
    } else {
3793
        if ($parents = get_records("course_meta","child_course",$course->id)) {
3794
            foreach ($parents as $parent) {
3795
                remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3796
            }
3797
            if ($showfeedback) {
3798
                notify("$strdeleted course_meta");
3799
            }
3800
        }
3801
    }
3802

    
3803
/// Delete questions and question categories
3804
    question_delete_course($course, $showfeedback);
3805

    
3806
/// Remove all data from gradebook
3807
    $context = get_context_instance(CONTEXT_COURSE, $courseid);
3808
    remove_course_grades($courseid, $showfeedback);
3809
    remove_grade_letters($context, $showfeedback);
3810

    
3811
    return $result;
3812
}
3813

    
3814
/**
3815
 * Change dates in module - used from course reset.
3816
 * @param strin $modname forum, assignent, etc
3817
 * @param array $fields array of date fields from mod table
3818
 * @param int $timeshift time difference
3819
 * @return success
3820
 */
3821
function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
3822
    global $CFG;
3823
    include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
3824

    
3825
    $return = true;
3826
    foreach ($fields as $field) {
3827
        $updatesql = "UPDATE {$CFG->prefix}$modname
3828
                          SET $field = $field + ($timeshift)
3829
                        WHERE course=$courseid AND $field<>0 AND $field<>0";
3830
        $return = execute_sql($updatesql, false) && $return;
3831
    }
3832

    
3833
    $refreshfunction = $modname.'_refresh_events';
3834
    if (function_exists($refreshfunction)) {
3835
        $refreshfunction($courseid);
3836
    }
3837

    
3838
    return $return;
3839
}
3840

    
3841
/**
3842
 * This function will empty a course of user data.
3843
 * It will retain the activities and the structure of the course.
3844
 * @param object $data an object containing all the settings including courseid (without magic quotes)
3845
 * @return array status array of array component, item, error
3846
 */
3847
function reset_course_userdata($data) {
3848
    global $CFG, $USER;
3849
    require_once($CFG->libdir.'/gradelib.php');
3850
    require_once($CFG->dirroot.'/group/lib.php');
3851

    
3852
    $data->courseid = $data->id;
3853
    $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3854

    
3855
    // calculate the time shift of dates
3856
    if (!empty($data->reset_start_date)) {
3857
        // time part of course startdate should be zero
3858
        $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
3859
    } else {
3860
        $data->timeshift = 0;
3861
    }
3862

    
3863
    // result array: component, item, error
3864
    $status = array();
3865

    
3866
    // start the resetting
3867
    $componentstr = get_string('general');
3868

    
3869
    // move the course start time
3870
    if (!empty($data->reset_start_date) and $data->timeshift) {
3871
        // change course start data
3872
        set_field('course', 'startdate', $data->reset_start_date, 'id', $data->courseid);
3873
        // update all course and group events - do not move activity events
3874
        $updatesql = "UPDATE {$CFG->prefix}event
3875
                         SET timestart = timestart + ({$data->timeshift})
3876
                       WHERE courseid={$data->courseid} AND instance=0";
3877
        execute_sql($updatesql, false);
3878

    
3879
        $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3880
    }
3881

    
3882
    if (!empty($data->reset_logs)) {
3883
        delete_records('log', 'course', $data->courseid);
3884
        $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
3885
    }
3886

    
3887
    if (!empty($data->reset_events)) {
3888
        delete_records('event', 'courseid', $data->courseid);
3889
        $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
3890
    }
3891

    
3892
    if (!empty($data->reset_notes)) {
3893
        require_once($CFG->dirroot.'/notes/lib.php');
3894
        note_delete_all($data->courseid);
3895
        $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
3896
    }
3897

    
3898
    $componentstr = get_string('roles');
3899

    
3900
    if (!empty($data->reset_roles_overrides)) {
3901
        $children = get_child_contexts($context);
3902
        foreach ($children as $child) {
3903
            delete_records('role_capabilities', 'contextid', $child->id);
3904
        }
3905
        delete_records('role_capabilities', 'contextid', $context->id);
3906
        //force refresh for logged in users
3907
        mark_context_dirty($context->path);
3908
        $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
3909
    }
3910

    
3911
    if (!empty($data->reset_roles_local)) {
3912
        $children = get_child_contexts($context);
3913
        foreach ($children as $child) {
3914
            role_unassign(0, 0, 0, $child->id);
3915
        }
3916
        //force refresh for logged in users
3917
        mark_context_dirty($context->path);
3918
        $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
3919
    }
3920

    
3921
    // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
3922
    $data->unenrolled = array();
3923
    if (!empty($data->reset_roles)) {
3924
        foreach($data->reset_roles as $roleid) {
3925
            if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) {
3926
                foreach ($users as $user) {
3927
                    role_unassign($roleid, $user->id, 0, $context->id);
3928
                    if (!has_capability('moodle/course:view', $context, $user->id)) {
3929
                        $data->unenrolled[$user->id] = $user->id;
3930
                    }
3931
                }
3932
            }
3933
        }
3934
    }
3935
    if (!empty($data->unenrolled)) {
3936
        $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol').' ('.count($data->unenrolled).')', 'error'=>false);
3937
    }
3938

    
3939

    
3940
    $componentstr = get_string('groups');
3941

    
3942
    // remove all group members
3943
    if (!empty($data->reset_groups_members)) {
3944
        groups_delete_group_members($data->courseid);
3945
        $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
3946
    }
3947

    
3948
    // remove all groups
3949
    if (!empty($data->reset_groups_remove)) {
3950
        groups_delete_groups($data->courseid, false);
3951
        $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
3952
    }
3953

    
3954
    // remove all grouping members
3955
    if (!empty($data->reset_groupings_members)) {
3956
        groups_delete_groupings_groups($data->courseid, false);
3957
        $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
3958
    }
3959

    
3960
    // remove all groupings
3961
    if (!empty($data->reset_groupings_remove)) {
3962
        groups_delete_groupings($data->courseid, false);
3963
        $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
3964
    }
3965

    
3966
    // Look in every instance of every module for data to delete
3967
    $unsupported_mods = array();
3968
    if ($allmods = get_records('modules') ) {
3969
        foreach ($allmods as $mod) {
3970
            $modname = $mod->name;
3971
            if (!count_records($modname, 'course', $data->courseid)) {
3972
                continue; // skip mods with no instances
3973
            }
3974
            $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
3975
            $moddeleteuserdata = $modname.'_reset_userdata';   // Function to delete user data
3976
            if (file_exists($modfile)) {
3977
                include_once($modfile);
3978
                if (function_exists($moddeleteuserdata)) {
3979
                    $modstatus = $moddeleteuserdata($data);
3980
                    if (is_array($modstatus)) {
3981
                        $status = array_merge($status, $modstatus);
3982
                    } else {
3983
                        debugging('Module '.$modname.' returned incorrect staus - must be an array!');
3984
                    }
3985
                } else {
3986
                    $unsupported_mods[] = $mod;
3987
                }
3988
            } else {
3989
                debugging('Missing lib.php in '.$modname.' module!');
3990
            }
3991
        }
3992
    }
3993

    
3994
    // mention unsupported mods
3995
    if (!empty($unsupported_mods)) {
3996
        foreach($unsupported_mods as $mod) {
3997
            $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
3998
        }
3999
    }
4000

    
4001

    
4002
    $componentstr = get_string('gradebook', 'grades');
4003
    // reset gradebook
4004
    if (!empty($data->reset_gradebook_items)) {
4005
        remove_course_grades($data->courseid, false);
4006
        grade_grab_course_grades($data->courseid);
4007
        grade_regrade_final_grades($data->courseid);
4008
        $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4009

    
4010
    } else if (!empty($data->reset_gradebook_grades)) {
4011
        grade_course_reset($data->courseid);
4012
        $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4013
    }
4014

    
4015
    return $status;
4016
}
4017

    
4018
function generate_email_processing_address($modid,$modargs) {
4019
    global $CFG;
4020

    
4021
    $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4022
    return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
4023
}
4024

    
4025
function moodle_process_email($modargs,$body) {
4026
    // the first char should be an unencoded letter. We'll take this as an action
4027
    switch ($modargs{0}) {
4028
        case 'B': { // bounce
4029
            list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4030
            if ($user = get_record_select("user","id=$userid","id,email")) {
4031
                // check the half md5 of their email
4032
                $md5check = substr(md5($user->email),0,16);
4033
                if ($md5check == substr($modargs, -16)) {
4034
                    set_bounce_count($user);
4035
                }
4036
                // else maybe they've already changed it?
4037
            }
4038
        }
4039
        break;
4040
        // maybe more later?
4041
    }
4042
}
4043

    
4044
/// CORRESPONDENCE  ////////////////////////////////////////////////
4045

    
4046
/**
4047
 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4048
 * @param $action string 'get', 'buffer', 'close' or 'flush'
4049
 * @return reference to mailer instance if 'get' used or nothing
4050
 */
4051
function &get_mailer($action='get') {
4052
    global $CFG;
4053

    
4054
    static $mailer  = null;
4055
    static $counter = 0;
4056

    
4057
    if (!isset($CFG->smtpmaxbulk)) {
4058
        $CFG->smtpmaxbulk = 1;
4059
    }
4060

    
4061
    if ($action == 'get') {
4062
        $prevkeepalive = false;
4063

    
4064
        if (isset($mailer) and $mailer->Mailer == 'smtp') {
4065
            if ($counter < $CFG->smtpmaxbulk and empty($mailer->error_count)) {
4066
                $counter++;
4067
                // reset the mailer
4068
                $mailer->Priority         = 3;
4069
                $mailer->CharSet          = 'UTF-8'; // our default
4070
                $mailer->ContentType      = "text/plain";
4071