Projet

Général

Profil

moodlelib.php

David Ragot, 18/11/2010 10:48

Télécharger (279 ko)

 
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
                $mailer->Encoding         = "8bit";
4072
                $mailer->From             = "root@localhost";
4073
                $mailer->FromName         = "Root User";
4074
                $mailer->Sender           = "";
4075
                $mailer->Subject          = "";
4076
                $mailer->Body             = "";
4077
                $mailer->AltBody          = "";
4078
                $mailer->ConfirmReadingTo = "";
4079

    
4080
                $mailer->ClearAllRecipients();
4081
                $mailer->ClearReplyTos();
4082
                $mailer->ClearAttachments();
4083
                $mailer->ClearCustomHeaders();
4084
                return $mailer;
4085
            }
4086

    
4087
            $prevkeepalive = $mailer->SMTPKeepAlive;
4088
            get_mailer('flush');
4089
        }
4090

    
4091
        include_once($CFG->libdir.'/phpmailer/class.phpmailer.php');
4092
        $mailer = new phpmailer();
4093

    
4094
        $counter = 1;
4095

    
4096
        $mailer->Version   = 'Moodle '.$CFG->version;         // mailer version
4097
        $mailer->PluginDir = $CFG->libdir.'/phpmailer/';      // plugin directory (eg smtp plugin)
4098
        $mailer->CharSet   = 'UTF-8';
4099

    
4100
        // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
4101
        // hmm, this is a bit hacky because LE should be private
4102
        if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
4103
            $mailer->LE = "\r\n";
4104
        } else {
4105
            $mailer->LE = "\n";
4106
        }
4107

    
4108
        if ($CFG->smtphosts == 'qmail') {
4109
            $mailer->IsQmail();                              // use Qmail system
4110

    
4111
        } else if (empty($CFG->smtphosts)) {
4112
            $mailer->IsMail();                               // use PHP mail() = sendmail
4113

    
4114
        } else {
4115
            $mailer->IsSMTP();                               // use SMTP directly
4116
            if (!empty($CFG->debugsmtp)) {
4117
                $mailer->SMTPDebug = true;
4118
            }
4119
            $mailer->Host          = $CFG->smtphosts;        // specify main and backup servers
4120
            $mailer->SMTPKeepAlive = $prevkeepalive;         // use previous keepalive
4121

    
4122
            if ($CFG->smtpuser) {                            // Use SMTP authentication
4123
                $mailer->SMTPAuth = true;
4124
                $mailer->Username = $CFG->smtpuser;
4125
                $mailer->Password = $CFG->smtppass;
4126
            }
4127
        }
4128

    
4129
        return $mailer;
4130
    }
4131

    
4132
    $nothing = null;
4133

    
4134
    // keep smtp session open after sending
4135
    if ($action == 'buffer') {
4136
        if (!empty($CFG->smtpmaxbulk)) {
4137
            get_mailer('flush');
4138
            $m =& get_mailer();
4139
            if ($m->Mailer == 'smtp') {
4140
                $m->SMTPKeepAlive = true;
4141
            }
4142
        }
4143
        return $nothing;
4144
    }
4145

    
4146
    // close smtp session, but continue buffering
4147
    if ($action == 'flush') {
4148
        if (isset($mailer) and $mailer->Mailer == 'smtp') {
4149
            if (!empty($mailer->SMTPDebug)) {
4150
                echo '<pre>'."\n";
4151
            }
4152
            $mailer->SmtpClose();
4153
            if (!empty($mailer->SMTPDebug)) {
4154
                echo '</pre>';
4155
            }
4156
        }
4157
        return $nothing;
4158
    }
4159

    
4160
    // close smtp session, do not buffer anymore
4161
    if ($action == 'close') {
4162
        if (isset($mailer) and $mailer->Mailer == 'smtp') {
4163
            get_mailer('flush');
4164
            $mailer->SMTPKeepAlive = false;
4165
        }
4166
        $mailer = null; // better force new instance
4167
        return $nothing;
4168
    }
4169
}
4170

    
4171
/**
4172
 * Send an email to a specified user
4173
 *
4174
 * @uses $CFG
4175
 * @uses $FULLME
4176
 * @uses $MNETIDPJUMPURL IdentityProvider(IDP) URL user hits to jump to mnet peer.
4177
 * @uses SITEID
4178
 * @param user $user  A {@link $USER} object
4179
 * @param user $from A {@link $USER} object
4180
 * @param string $subject plain text subject line of the email
4181
 * @param string $messagetext plain text version of the message
4182
 * @param string $messagehtml complete html version of the message (optional)
4183
 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4184
 * @param string $attachname the name of the file (extension indicates MIME)
4185
 * @param bool $usetrueaddress determines whether $from email address should
4186
 *          be sent out. Will be overruled by user profile setting for maildisplay
4187
 * @param int $wordwrapwidth custom word wrap width
4188
 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4189
 *          was blocked by user and "false" if there was another sort of error.
4190
 */
4191
function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4192

    
4193
    global $CFG, $FULLME, $MNETIDPJUMPURL;
4194
    static $mnetjumps = array();
4195

    
4196
    if (empty($user) || empty($user->email)) {
4197
        return false;
4198
    }
4199

    
4200
    if (!empty($user->deleted)) {
4201
        // do not mail delted users
4202
        return false;
4203
    }
4204

    
4205
    if (!empty($CFG->noemailever)) {
4206
        // hidden setting for development sites, set in config.php if needed
4207
        return true;
4208
    }
4209

    
4210
    // skip mail to suspended users
4211
    if (isset($user->auth) && $user->auth=='nologin') {
4212
        return true;
4213
    }
4214

    
4215
    if (!empty($user->emailstop)) {
4216
        return 'emailstop';
4217
    }
4218

    
4219
    if (over_bounce_threshold($user)) {
4220
        error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
4221
        return false;
4222
    }
4223

    
4224
    // If the user is a remote mnet user, parse the email text for URL to the
4225
    // wwwroot and modify the url to direct the user's browser to login at their
4226
    // home site (identity provider - idp) before hitting the link itself
4227
    if (is_mnet_remote_user($user)) {
4228
        require_once($CFG->dirroot.'/mnet/lib.php');
4229
        // Form the request url to hit the idp's jump.php
4230
        if (isset($mnetjumps[$user->mnethostid])) {
4231
            $MNETIDPJUMPURL = $mnetjumps[$user->mnethostid];
4232
        } else {
4233
            $idp = mnet_get_peer_host($user->mnethostid);
4234
            $idpjumppath = '/auth/mnet/jump.php';
4235
            $MNETIDPJUMPURL = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
4236
            $mnetjumps[$user->mnethostid] = $MNETIDPJUMPURL;
4237
        }
4238

    
4239
        $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4240
                'mnet_sso_apply_indirection',
4241
                $messagetext);
4242
        $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4243
                'mnet_sso_apply_indirection',
4244
                $messagehtml);
4245
    }
4246
    $mail =& get_mailer();
4247

    
4248
    if (!empty($mail->SMTPDebug)) {
4249
        echo '<pre>' . "\n";
4250
    }
4251

    
4252
/// We are going to use textlib services here
4253
    $textlib = textlib_get_instance();
4254

    
4255
    $supportuser = generate_email_supportuser();
4256

    
4257
    // make up an email address for handling bounces
4258
    if (!empty($CFG->handlebounces)) {
4259
        $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
4260
        $mail->Sender = generate_email_processing_address(0,$modargs);
4261
    } else {
4262
        $mail->Sender = $supportuser->email;
4263
    }
4264

    
4265
    if (is_string($from)) { // So we can pass whatever we want if there is need
4266
        $mail->From     = $CFG->noreplyaddress;
4267
        $mail->FromName = $from;
4268
    } else if ($usetrueaddress and $from->maildisplay) {
4269
        $mail->From     = stripslashes($from->email);
4270
        $mail->FromName = fullname($from);
4271
    } else {
4272
        $mail->From     = $CFG->noreplyaddress;
4273
        $mail->FromName = fullname($from);
4274
        if (empty($replyto)) {
4275
            $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
4276
        }
4277
    }
4278

    
4279
    if (!empty($replyto)) {
4280
        $mail->AddReplyTo($replyto,$replytoname);
4281
    }
4282

    
4283
    $mail->Subject = substr(stripslashes($subject), 0, 900);
4284

    
4285
    $mail->AddAddress(stripslashes($user->email), fullname($user) );
4286

    
4287
    $mail->WordWrap = $wordwrapwidth;                   // set word wrap
4288

    
4289
    if (!empty($from->customheaders)) {                 // Add custom headers
4290
        if (is_array($from->customheaders)) {
4291
            foreach ($from->customheaders as $customheader) {
4292
                $mail->AddCustomHeader($customheader);
4293
            }
4294
        } else {
4295
            $mail->AddCustomHeader($from->customheaders);
4296
        }
4297
    }
4298

    
4299
    if (!empty($from->priority)) {
4300
        $mail->Priority = $from->priority;
4301
    }
4302

    
4303
    if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
4304
        $mail->IsHTML(true);
4305
        $mail->Encoding = 'quoted-printable';           // Encoding to use
4306
        $mail->Body    =  $messagehtml;
4307
        $mail->AltBody =  "\n$messagetext\n";
4308
    } else {
4309
        $mail->IsHTML(false);
4310
        $mail->Body =  "\n$messagetext\n";
4311
    }
4312

    
4313
    if ($attachment && $attachname) {
4314
        if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
4315
            $mail->AddAddress($supportuser->email, fullname($supportuser, true) );
4316
            $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4317
        } else {
4318
            require_once($CFG->libdir.'/filelib.php');
4319
            $mimetype = mimeinfo('type', $attachname);
4320
            $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
4321
        }
4322
    }
4323

    
4324

    
4325

    
4326
/// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
4327
/// encoding to the specified one
4328
    if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
4329
    /// Set it to site mail charset
4330
        $charset = $CFG->sitemailcharset;
4331
    /// Overwrite it with the user mail charset
4332
        if (!empty($CFG->allowusermailcharset)) {
4333
            if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
4334
                $charset = $useremailcharset;
4335
            }
4336
        }
4337
    /// If it has changed, convert all the necessary strings
4338
        $charsets = get_list_of_charsets();
4339
        unset($charsets['UTF-8']);
4340
        if (in_array($charset, $charsets)) {
4341
        /// Save the new mail charset
4342
            $mail->CharSet = $charset;
4343
        /// And convert some strings
4344
            $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
4345
            foreach ($mail->ReplyTo as $key => $rt) {                                      //ReplyTo Names
4346
                $mail->ReplyTo[$key][1] = $textlib->convert($rt[1], 'utf-8', $mail->CharSet);
4347
            }
4348
            $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet);   //Subject
4349
            foreach ($mail->to as $key => $to) {
4350
                $mail->to[$key][1] = $textlib->convert($to[1], 'utf-8', $mail->CharSet);      //To Names
4351
            }
4352
            $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet);         //Body
4353
            $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet);   //Subject
4354
        }
4355
    }
4356

    
4357
    if ($mail->Send()) {
4358
        set_send_count($user);
4359
        $mail->IsSMTP();                               // use SMTP directly
4360
        if (!empty($mail->SMTPDebug)) {
4361
            echo '</pre>';
4362
        }
4363
        return true;
4364
    } else {
4365
        mtrace('ERROR: '. $mail->ErrorInfo);
4366
        add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
4367
        if (!empty($mail->SMTPDebug)) {
4368
            echo '</pre>';
4369
        }
4370
        return false;
4371
    }
4372
}
4373

    
4374
/**
4375
 * Generate a signoff for emails based on support settings
4376
 *
4377
 */
4378
function generate_email_signoff() {
4379
    global $CFG;
4380

    
4381
    $signoff = "\n";
4382
    if (!empty($CFG->supportname)) {
4383
        $signoff .= $CFG->supportname."\n";
4384
    }
4385
    if (!empty($CFG->supportemail)) {
4386
        $signoff .= $CFG->supportemail."\n";
4387
    }
4388
    if (!empty($CFG->supportpage)) {
4389
        $signoff .= $CFG->supportpage."\n";
4390
    }
4391
    return $signoff;
4392
}
4393

    
4394
/**
4395
 * Generate a fake user for emails based on support settings
4396
 *
4397
 */
4398
function generate_email_supportuser() {
4399

    
4400
    global $CFG;
4401

    
4402
    static $supportuser;
4403

    
4404
    if (!empty($supportuser)) {
4405
        return $supportuser;
4406
    }
4407

    
4408
    $supportuser = new object;
4409
    $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
4410
    $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
4411
    $supportuser->lastname = '';
4412
    $supportuser->maildisplay = true;
4413

    
4414
    return $supportuser;
4415
}
4416

    
4417

    
4418
/**
4419
 * Sets specified user's password and send the new password to the user via email.
4420
 *
4421
 * @uses $CFG
4422
 * @param user $user A {@link $USER} object
4423
 * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
4424
 *          was blocked by user and "false" if there was another sort of error.
4425
 */
4426
function setnew_password_and_mail($user) {
4427

    
4428
    global $CFG;
4429

    
4430
    $site  = get_site();
4431

    
4432
    $supportuser = generate_email_supportuser();
4433

    
4434
    $newpassword = generate_password();
4435

    
4436
    if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
4437
        trigger_error('Could not set user password!');
4438
        return false;
4439
    }
4440

    
4441
    $a = new object();
4442
    $a->firstname   = fullname($user, true);
4443
    $a->sitename    = format_string($site->fullname);
4444
    $a->username    = $user->username;
4445
    $a->newpassword = $newpassword;
4446
    $a->link        = $CFG->wwwroot .'/login/';
4447
    $a->signoff     = generate_email_signoff();
4448

    
4449
    $message = get_string('newusernewpasswordtext', '', $a);
4450

    
4451
    $subject  = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
4452

    
4453
    return email_to_user($user, $supportuser, $subject, $message);
4454

    
4455
}
4456

    
4457
/**
4458
 * Resets specified user's password and send the new password to the user via email.
4459
 *
4460
 * @uses $CFG
4461
 * @param user $user A {@link $USER} object
4462
 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4463
 *          was blocked by user and "false" if there was another sort of error.
4464
 */
4465
function reset_password_and_mail($user) {
4466

    
4467
    global $CFG;
4468

    
4469
    $site  = get_site();
4470
    $supportuser = generate_email_supportuser();
4471

    
4472
    $userauth = get_auth_plugin($user->auth);
4473
    if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
4474
        trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4475
        return false;
4476
    }
4477

    
4478
    $newpassword = generate_password();
4479

    
4480
    if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
4481
        error("Could not set user password!");
4482
    }
4483

    
4484
    $a = new object();
4485
    $a->firstname   = $user->firstname;
4486
    $a->lastname    = $user->lastname;
4487
    $a->sitename    = format_string($site->fullname);
4488
    $a->username    = $user->username;
4489
    $a->newpassword = $newpassword;
4490
    $a->link        = $CFG->httpswwwroot .'/login/change_password.php';
4491
    $a->signoff     = generate_email_signoff();
4492

    
4493
    $message = get_string('newpasswordtext', '', $a);
4494

    
4495
    $subject  = format_string($site->fullname) .': '. get_string('changedpassword');
4496

    
4497
    return email_to_user($user, $supportuser, $subject, $message);
4498

    
4499
}
4500

    
4501
/**
4502
 * Send email to specified user with confirmation text and activation link.
4503
 *
4504
 * @uses $CFG
4505
 * @param user $user A {@link $USER} object
4506
 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4507
 *          was blocked by user and "false" if there was another sort of error.
4508
 */
4509
 function send_confirmation_email($user) {
4510

    
4511
    global $CFG;
4512

    
4513
    $site = get_site();
4514
    $supportuser = generate_email_supportuser();
4515

    
4516
    $data = new object();
4517
    $data->firstname = fullname($user);
4518
    $data->sitename = format_string($site->fullname);
4519
    $data->admin = generate_email_signoff();
4520

    
4521
    $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
4522

    
4523
    $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
4524
    $message     = get_string('emailconfirmation', '', $data);
4525
    $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4526

    
4527
    $user->mailformat = 1;  // Always send HTML version as well
4528

    
4529
    return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4530

    
4531
}
4532

    
4533
/**
4534
 * send_password_change_confirmation_email.
4535
 *
4536
 * @uses $CFG
4537
 * @param user $user A {@link $USER} object
4538
 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4539
 *          was blocked by user and "false" if there was another sort of error.
4540
 */
4541
function send_password_change_confirmation_email($user) {
4542

    
4543
    global $CFG;
4544

    
4545
    $site = get_site();
4546
    $supportuser = generate_email_supportuser();
4547

    
4548
    $data = new object();
4549
    $data->firstname = $user->firstname;
4550
    $data->lastname  = $user->lastname;
4551
    $data->sitename  = format_string($site->fullname);
4552
    $data->link      = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
4553
    $data->admin     = generate_email_signoff();
4554

    
4555
    $message = get_string('emailpasswordconfirmation', '', $data);
4556
    $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
4557

    
4558
    return email_to_user($user, $supportuser, $subject, $message);
4559

    
4560
}
4561

    
4562
/**
4563
 * send_password_change_info.
4564
 *
4565
 * @uses $CFG
4566
 * @param user $user A {@link $USER} object
4567
 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4568
 *          was blocked by user and "false" if there was another sort of error.
4569
 */
4570
function send_password_change_info($user) {
4571

    
4572
    global $CFG;
4573

    
4574
    $site = get_site();
4575
    $supportuser = generate_email_supportuser();
4576
    $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4577

    
4578
    $data = new object();
4579
    $data->firstname = $user->firstname;
4580
    $data->lastname  = $user->lastname;
4581
    $data->sitename = format_string($site->fullname);
4582
    $data->admin = generate_email_signoff();
4583

    
4584
    $userauth = get_auth_plugin($user->auth);
4585

    
4586
    if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
4587
        $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4588
        $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4589
        return email_to_user($user, $supportuser, $subject, $message);
4590
    }
4591

    
4592
    if ($userauth->can_change_password() and $userauth->change_password_url()) {
4593
        // we have some external url for password changing
4594
        $data->link .= $userauth->change_password_url();
4595

    
4596
    } else {
4597
        //no way to change password, sorry
4598
        $data->link = '';
4599
    }
4600

    
4601
    if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
4602
        $message = get_string('emailpasswordchangeinfo', '', $data);
4603
        $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4604
    } else {
4605
        $message = get_string('emailpasswordchangeinfofail', '', $data);
4606
        $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4607
    }
4608

    
4609
    return email_to_user($user, $supportuser, $subject, $message);
4610

    
4611
}
4612

    
4613
/**
4614
 * Check that an email is allowed.  It returns an error message if there
4615
 * was a problem.
4616
 *
4617
 * @uses $CFG
4618
 * @param  string $email Content of email
4619
 * @return string|false
4620
 */
4621
function email_is_not_allowed($email) {
4622

    
4623
    global $CFG;
4624

    
4625
    if (!empty($CFG->allowemailaddresses)) {
4626
        $allowed = explode(' ', $CFG->allowemailaddresses);
4627
        foreach ($allowed as $allowedpattern) {
4628
            $allowedpattern = trim($allowedpattern);
4629
            if (!$allowedpattern) {
4630
                continue;
4631
            }
4632
            if (strpos($allowedpattern, '.') === 0) {
4633
                if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
4634
                    // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4635
                    return false;
4636
                }
4637

    
4638
            } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match!   (bug 5250)
4639
                return false;
4640
            }
4641
        }
4642
        return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
4643

    
4644
    } else if (!empty($CFG->denyemailaddresses)) {
4645
        $denied = explode(' ', $CFG->denyemailaddresses);
4646
        foreach ($denied as $deniedpattern) {
4647
            $deniedpattern = trim($deniedpattern);
4648
            if (!$deniedpattern) {
4649
                continue;
4650
            }
4651
            if (strpos($deniedpattern, '.') === 0) {
4652
                if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
4653
                    // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4654
                    return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4655
                }
4656

    
4657
            } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match!   (bug 5250)
4658
                return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4659
            }
4660
        }
4661
    }
4662

    
4663
    return false;
4664
}
4665

    
4666
function email_welcome_message_to_user($course, $user=NULL) {
4667
    global $CFG, $USER;
4668

    
4669
    if (isset($CFG->sendcoursewelcomemessage) and !$CFG->sendcoursewelcomemessage) {
4670
        return;
4671
    }
4672

    
4673
    if (empty($user)) {
4674
        if (!isloggedin()) {
4675
            return false;
4676
        }
4677
        $user = $USER;
4678
    }
4679

    
4680
    if (!empty($course->welcomemessage)) {
4681
        $message = $course->welcomemessage;
4682
    } else {
4683
        $a = new Object();
4684
        $a->coursename = $course->fullname;
4685
        $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id";
4686
        $message = get_string("welcometocoursetext", "", $a);
4687
    }
4688

    
4689
    /// If you don't want a welcome message sent, then make the message string blank.
4690
    if (!empty($message)) {
4691
        $subject = get_string('welcometocourse', '', format_string($course->fullname));
4692

    
4693
        if (! $teacher = get_teacher($course->id)) {
4694
            $teacher = get_admin();
4695
        }
4696
        email_to_user($user, $teacher, $subject, $message);
4697
    }
4698
}
4699

    
4700
/// FILE HANDLING  /////////////////////////////////////////////
4701

    
4702

    
4703
/**
4704
 * Makes an upload directory for a particular module.
4705
 *
4706
 * @uses $CFG
4707
 * @param int $courseid The id of the course in question - maps to id field of 'course' table.
4708
 * @return string|false Returns full path to directory if successful, false if not
4709
 */
4710
function make_mod_upload_directory($courseid) {
4711
    global $CFG;
4712

    
4713
    if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
4714
        return false;
4715
    }
4716

    
4717
    $strreadme = get_string('readme');
4718

    
4719
    if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
4720
        copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4721
    } else {
4722
        copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4723
    }
4724
    return $moddata;
4725
}
4726

    
4727
/**
4728
 * Makes a directory for a particular user.
4729
 *
4730
 * @uses $CFG
4731
 * @param int $userid The id of the user in question - maps to id field of 'user' table.
4732
 * @param bool $test Whether we are only testing the return value (do not create the directory)
4733
 * @return string|false Returns full path to directory if successful, false if not
4734
 */
4735
function make_user_directory($userid, $test=false) {
4736
    global $CFG;
4737

    
4738
    if (is_bool($userid) || $userid < 0 || !ereg('^[0-9]{1,10}$', $userid) || $userid > 2147483647) {
4739
        if (!$test) {
4740
            notify("Given userid was not a valid integer! (" . gettype($userid) . " $userid)");
4741
        }
4742
        return false;
4743
    }
4744

    
4745
    // Generate a two-level path for the userid. First level groups them by slices of 1000 users, second level is userid
4746
    $level1 = floor($userid / 1000) * 1000;
4747

    
4748
    $userdir = "user/$level1/$userid";
4749
    if ($test) {
4750
        return $CFG->dataroot . '/' . $userdir;
4751
    } else {
4752
        return make_upload_directory($userdir);
4753
    }
4754
}
4755

    
4756
/**
4757
 * Returns an array of full paths to user directories, indexed by their userids.
4758
 *
4759
 * @param bool $only_non_empty Only return directories that contain files
4760
 * @param bool $legacy Search for user directories in legacy location (dataroot/users/userid) instead of (dataroot/user/section/userid)
4761
 * @return array An associative array: userid=>array(basedir => $basedir, userfolder => $userfolder)
4762
 */
4763
function get_user_directories($only_non_empty=true, $legacy=false) {
4764
    global $CFG;
4765

    
4766
    $rootdir = $CFG->dataroot."/user";
4767

    
4768
    if ($legacy) {
4769
        $rootdir = $CFG->dataroot."/users";
4770
    }
4771
    $dirlist = array();
4772

    
4773
    //Check if directory exists
4774
    if (check_dir_exists($rootdir, true)) {
4775
        if ($legacy) {
4776
            if ($userlist = get_directory_list($rootdir, '', true, true, false)) {
4777
                foreach ($userlist as $userid) {
4778
                    $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $userid);
4779
                }
4780
            } else {
4781
                notify("no directories found under $rootdir");
4782
            }
4783
        } else {
4784
            if ($grouplist =get_directory_list($rootdir, '', true, true, false)) { // directories will be in the form 0, 1000, 2000 etc...
4785
                foreach ($grouplist as $group) {
4786
                    if ($userlist = get_directory_list("$rootdir/$group", '', true, true, false)) {
4787
                        foreach ($userlist as $userid) {
4788
                            $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $group . '/' . $userid);
4789
                        }
4790
                    }
4791
                }
4792
            }
4793
        }
4794
    } else {
4795
        notify("$rootdir does not exist!");
4796
        return false;
4797
    }
4798
    return $dirlist;
4799
}
4800

    
4801
/**
4802
 * Returns current name of file on disk if it exists.
4803
 *
4804
 * @param string $newfile File to be verified
4805
 * @return string Current name of file on disk if true
4806
 */
4807
function valid_uploaded_file($newfile) {
4808
    if (empty($newfile)) {
4809
        return '';
4810
    }
4811
    if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
4812
        return $newfile['tmp_name'];
4813
    } else {
4814
        return '';
4815
    }
4816
}
4817

    
4818
/**
4819
 * Returns the maximum size for uploading files.
4820
 *
4821
 * There are seven possible upload limits:
4822
 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
4823
 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
4824
 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
4825
 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
4826
 * 5. by the Moodle admin in $CFG->maxbytes
4827
 * 6. by the teacher in the current course $course->maxbytes
4828
 * 7. by the teacher for the current module, eg $assignment->maxbytes
4829
 *
4830
 * These last two are passed to this function as arguments (in bytes).
4831
 * Anything defined as 0 is ignored.
4832
 * The smallest of all the non-zero numbers is returned.
4833
 *
4834
 * @param int $sizebytes ?
4835
 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4836
 * @param int $modulebytes Current module ->maxbytes (in bytes)
4837
 * @return int The maximum size for uploading files.
4838
 * @todo Finish documenting this function
4839
 */
4840
function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4841

    
4842
    if (! $filesize = ini_get('upload_max_filesize')) {
4843
        $filesize = '5M';
4844
    }
4845
    $minimumsize = get_real_size($filesize);
4846

    
4847
    if ($postsize = ini_get('post_max_size')) {
4848
        $postsize = get_real_size($postsize);
4849
        if ($postsize < $minimumsize) {
4850
            $minimumsize = $postsize;
4851
        }
4852
    }
4853

    
4854
    if ($sitebytes and $sitebytes < $minimumsize) {
4855
        $minimumsize = $sitebytes;
4856
    }
4857

    
4858
    if ($coursebytes and $coursebytes < $minimumsize) {
4859
        $minimumsize = $coursebytes;
4860
    }
4861

    
4862
    if ($modulebytes and $modulebytes < $minimumsize) {
4863
        $minimumsize = $modulebytes;
4864
    }
4865

    
4866
    return $minimumsize;
4867
}
4868

    
4869
/**
4870
 * Related to {@link get_max_upload_file_size()} - this function returns an
4871
 * array of possible sizes in an array, translated to the
4872
 * local language.
4873
 *
4874
 * @uses SORT_NUMERIC
4875
 * @param int $sizebytes ?
4876
 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4877
 * @param int $modulebytes Current module ->maxbytes (in bytes)
4878
 * @return int
4879
 * @todo Finish documenting this function
4880
 */
4881
function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4882
    global $CFG;
4883

    
4884
    if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
4885
        return array();
4886
    }
4887

    
4888
    $filesize[$maxsize] = display_size($maxsize);
4889

    
4890
    $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
4891
                      5242880, 10485760, 20971520, 52428800, 104857600);
4892

    
4893
    // Allow maxbytes to be selected if it falls outside the above boundaries
4894
    if( isset($CFG->maxbytes) && !in_array($CFG->maxbytes, $sizelist) ){
4895
            $sizelist[] = $CFG->maxbytes;
4896
    }
4897

    
4898
    foreach ($sizelist as $sizebytes) {
4899
       if ($sizebytes < $maxsize) {
4900
           $filesize[$sizebytes] = display_size($sizebytes);
4901
       }
4902
    }
4903

    
4904
    krsort($filesize, SORT_NUMERIC);
4905

    
4906
    return $filesize;
4907
}
4908

    
4909
/**
4910
 * If there has been an error uploading a file, print the appropriate error message
4911
 * Numerical constants used as constant definitions not added until PHP version 4.2.0
4912
 *
4913
 * $filearray is a 1-dimensional sub-array of the $_FILES array
4914
 * eg $filearray = $_FILES['userfile1']
4915
 * If left empty then the first element of the $_FILES array will be used
4916
 *
4917
 * @uses $_FILES
4918
 * @param array $filearray  A 1-dimensional sub-array of the $_FILES array
4919
 * @param bool $returnerror If true then a string error message will be returned. Otherwise the user will be notified of the error in a notify() call.
4920
 * @return bool|string
4921
 */
4922
function print_file_upload_error($filearray = '', $returnerror = false) {
4923

    
4924
    if ($filearray == '' or !isset($filearray['error'])) {
4925

    
4926
        if (empty($_FILES)) return false;
4927

    
4928
        $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
4929
        $filearray = array_shift($files); /// use first element of array
4930
    }
4931

    
4932
    switch ($filearray['error']) {
4933

    
4934
        case 0: // UPLOAD_ERR_OK
4935
            if ($filearray['size'] > 0) {
4936
                $errmessage = get_string('uploadproblem', $filearray['name']);
4937
            } else {
4938
                $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
4939
            }
4940
            break;
4941

    
4942
        case 1: // UPLOAD_ERR_INI_SIZE
4943
            $errmessage = get_string('uploadserverlimit');
4944
            break;
4945

    
4946
        case 2: // UPLOAD_ERR_FORM_SIZE
4947
            $errmessage = get_string('uploadformlimit');
4948
            break;
4949

    
4950
        case 3: // UPLOAD_ERR_PARTIAL
4951
            $errmessage = get_string('uploadpartialfile');
4952
            break;
4953

    
4954
        case 4: // UPLOAD_ERR_NO_FILE
4955
            $errmessage = get_string('uploadnofilefound');
4956
            break;
4957

    
4958
        default:
4959
            $errmessage = get_string('uploadproblem', $filearray['name']);
4960
    }
4961

    
4962
    if ($returnerror) {
4963
        return $errmessage;
4964
    } else {
4965
        notify($errmessage);
4966
        return true;
4967
    }
4968

    
4969
}
4970

    
4971
/**
4972
 * handy function to loop through an array of files and resolve any filename conflicts
4973
 * both in the array of filenames and for what is already on disk.
4974
 * not really compatible with the similar function in uploadlib.php
4975
 * but this could be used for files/index.php for moving files around.
4976
 */
4977

    
4978
function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
4979
    foreach ($files as $k => $f) {
4980
        if (check_potential_filename($destination,$f,$files)) {
4981
            $bits = explode('.', $f);
4982
            for ($i = 1; true; $i++) {
4983
                $try = sprintf($format, $bits[0], $i, $bits[1]);
4984
                if (!check_potential_filename($destination,$try,$files)) {
4985
                    $files[$k] = $try;
4986
                    break;
4987
                }
4988
            }
4989
        }
4990
    }
4991
    return $files;
4992
}
4993

    
4994
/**
4995
 * @used by resolve_filename_collisions
4996
 */
4997
function check_potential_filename($destination,$filename,$files) {
4998
    if (file_exists($destination.'/'.$filename)) {
4999
        return true;
5000
    }
5001
    if (count(array_keys($files,$filename)) > 1) {
5002
        return true;
5003
    }
5004
    return false;
5005
}
5006

    
5007

    
5008
/**
5009
 * Returns an array with all the filenames in
5010
 * all subdirectories, relative to the given rootdir.
5011
 * If excludefile is defined, then that file/directory is ignored
5012
 * If getdirs is true, then (sub)directories are included in the output
5013
 * If getfiles is true, then files are included in the output
5014
 * (at least one of these must be true!)
5015
 *
5016
 * @param string $rootdir  ?
5017
 * @param string $excludefile  If defined then the specified file/directory is ignored
5018
 * @param bool $descend  ?
5019
 * @param bool $getdirs  If true then (sub)directories are included in the output
5020
 * @param bool $getfiles  If true then files are included in the output
5021
 * @return array An array with all the filenames in
5022
 * all subdirectories, relative to the given rootdir
5023
 * @todo Finish documenting this function. Add examples of $excludefile usage.
5024
 */
5025
function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5026

    
5027
    $dirs = array();
5028

    
5029
    if (!$getdirs and !$getfiles) {   // Nothing to show
5030
        return $dirs;
5031
    }
5032

    
5033
    if (!is_dir($rootdir)) {          // Must be a directory
5034
        return $dirs;
5035
    }
5036

    
5037
    if (!$dir = opendir($rootdir)) {  // Can't open it for some reason
5038
        return $dirs;
5039
    }
5040

    
5041
    if (!is_array($excludefiles)) {
5042
        $excludefiles = array($excludefiles);
5043
    }
5044

    
5045
    while (false !== ($file = readdir($dir))) {
5046
        $firstchar = substr($file, 0, 1);
5047
        if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5048
            continue;
5049
        }
5050
        $fullfile = $rootdir .'/'. $file;
5051
        if (filetype($fullfile) == 'dir') {
5052
            if ($getdirs) {
5053
                $dirs[] = $file;
5054
            }
5055
            if ($descend) {
5056
                $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5057
                foreach ($subdirs as $subdir) {
5058
                    $dirs[] = $file .'/'. $subdir;
5059
                }
5060
            }
5061
        } else if ($getfiles) {
5062
            $dirs[] = $file;
5063
        }
5064
    }
5065
    closedir($dir);
5066

    
5067
    asort($dirs);
5068

    
5069
    return $dirs;
5070
}
5071

    
5072

    
5073
/**
5074
 * Adds up all the files in a directory and works out the size.
5075
 *
5076
 * @param string $rootdir  ?
5077
 * @param string $excludefile  ?
5078
 * @return array
5079
 * @todo Finish documenting this function
5080
 */
5081
function get_directory_size($rootdir, $excludefile='') {
5082

    
5083
    global $CFG;
5084

    
5085
    // do it this way if we can, it's much faster
5086
    if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5087
        $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5088
        $output = null;
5089
        $return = null;
5090
        exec($command,$output,$return);
5091
        if (is_array($output)) {
5092
            return get_real_size(intval($output[0]).'k'); // we told it to return k.
5093
        }
5094
    }
5095

    
5096
    if (!is_dir($rootdir)) {          // Must be a directory
5097
        return 0;
5098
    }
5099

    
5100
    if (!$dir = @opendir($rootdir)) {  // Can't open it for some reason
5101
        return 0;
5102
    }
5103

    
5104
    $size = 0;
5105

    
5106
    while (false !== ($file = readdir($dir))) {
5107
        $firstchar = substr($file, 0, 1);
5108
        if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5109
            continue;
5110
        }
5111
        $fullfile = $rootdir .'/'. $file;
5112
        if (filetype($fullfile) == 'dir') {
5113
            $size += get_directory_size($fullfile, $excludefile);
5114
        } else {
5115
            $size += filesize($fullfile);
5116
        }
5117
    }
5118
    closedir($dir);
5119

    
5120
    return $size;
5121
}
5122

    
5123
/**
5124
 * Converts bytes into display form
5125
 *
5126
 * @param string $size  ?
5127
 * @return string
5128
 * @staticvar string $gb Localized string for size in gigabytes
5129
 * @staticvar string $mb Localized string for size in megabytes
5130
 * @staticvar string $kb Localized string for size in kilobytes
5131
 * @staticvar string $b Localized string for size in bytes
5132
 * @todo Finish documenting this function. Verify return type.
5133
 */
5134
function display_size($size) {
5135

    
5136
    static $gb, $mb, $kb, $b;
5137

    
5138
    if (empty($gb)) {
5139
        $gb = get_string('sizegb');
5140
        $mb = get_string('sizemb');
5141
        $kb = get_string('sizekb');
5142
        $b  = get_string('sizeb');
5143
    }
5144

    
5145
    if ($size >= 1073741824) {
5146
        $size = round($size / 1073741824 * 10) / 10 . $gb;
5147
    } else if ($size >= 1048576) {
5148
        $size = round($size / 1048576 * 10) / 10 . $mb;
5149
    } else if ($size >= 1024) {
5150
        $size = round($size / 1024 * 10) / 10 . $kb;
5151
    } else {
5152
        $size = $size .' '. $b;
5153
    }
5154
    return $size;
5155
}
5156

    
5157
/**
5158
 * Cleans a given filename by removing suspicious or troublesome characters
5159
 * Only these are allowed: alphanumeric _ - .
5160
 * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
5161
 *
5162
 * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
5163
 *          because native zip binaries do weird character conversions. Use PHP zipping instead.
5164
 *
5165
 * @param string $string  file name
5166
 * @return string cleaned file name
5167
 */
5168
function clean_filename($string) {
5169
    global $CFG;
5170
    if (empty($CFG->unicodecleanfilename)) {
5171
        $textlib = textlib_get_instance();
5172
        $string = $textlib->specialtoascii($string);
5173
        $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
5174
    } else {
5175
        //clean only ascii range
5176
        $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
5177
    }
5178
    $string = preg_replace("/_+/", '_', $string);
5179
    $string = preg_replace("/\.\.+/", '.', $string);
5180
    return $string;
5181
}
5182

    
5183

    
5184
/// STRING TRANSLATION  ////////////////////////////////////////
5185

    
5186
/**
5187
 * Returns the code for the current language
5188
 *
5189
 * @uses $CFG
5190
 * @param $USER
5191
 * @param $SESSION
5192
 * @return string
5193
 */
5194
function current_language() {
5195
    global $CFG, $USER, $SESSION, $COURSE;
5196

    
5197
    if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) {    // Course language can override all other settings for this page
5198
        $return = $COURSE->lang;
5199

    
5200
    } else if (!empty($SESSION->lang)) {    // Session language can override other settings
5201
        $return = $SESSION->lang;
5202

    
5203
    } else if (!empty($USER->lang)) {
5204
        $return = $USER->lang;
5205

    
5206
    } else {
5207
        $return = $CFG->lang;
5208
    }
5209

    
5210
    if ($return == 'en') {
5211
        $return = 'en_utf8';
5212
    }
5213

    
5214
    return $return;
5215
}
5216

    
5217
/**
5218
 * Prints out a translated string.
5219
 *
5220
 * Prints out a translated string using the return value from the {@link get_string()} function.
5221
 *
5222
 * Example usage of this function when the string is in the moodle.php file:<br/>
5223
 * <code>
5224
 * echo '<strong>';
5225
 * print_string('wordforstudent');
5226
 * echo '</strong>';
5227
 * </code>
5228
 *
5229
 * Example usage of this function when the string is not in the moodle.php file:<br/>
5230
 * <code>
5231
 * echo '<h1>';
5232
 * print_string('typecourse', 'calendar');
5233
 * echo '</h1>';
5234
 * </code>
5235
 *
5236
 * @param string $identifier The key identifier for the localized string
5237
 * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
5238
 * @param mixed $a An object, string or number that can be used
5239
 * within translation strings
5240
 */
5241
function print_string($identifier, $module='', $a=NULL) {
5242
    echo get_string($identifier, $module, $a);
5243
}
5244

    
5245
/**
5246
 * fix up the optional data in get_string()/print_string() etc
5247
 * ensure possible sprintf() format characters are escaped correctly
5248
 * needs to handle arbitrary strings and objects
5249
 * @param mixed $a An object, string or number that can be used
5250
 * @return mixed the supplied parameter 'cleaned'
5251
 */
5252
function clean_getstring_data( $a ) {
5253
    if (is_string($a)) {
5254
        return str_replace( '%','%%',$a );
5255
    }
5256
    elseif (is_object($a)) {
5257
        $a_vars = get_object_vars( $a );
5258
        $new_a_vars = array();
5259
        foreach ($a_vars as $fname => $a_var) {
5260
            $new_a_vars[$fname] = clean_getstring_data( $a_var );
5261
        }
5262
        return (object)$new_a_vars;
5263
    }
5264
    else {
5265
        return $a;
5266
    }
5267
}
5268

    
5269
/**
5270
 * @return array places to look for lang strings based on the prefix to the
5271
 * module name. For example qtype_ in question/type. Used by get_string and
5272
 * help.php.
5273
 */
5274
function places_to_search_for_lang_strings() {
5275
    global $CFG;
5276

    
5277
    return array(
5278
        '__exceptions' => array('moodle', 'langconfig'),
5279
        'assignment_' => array('mod/assignment/type'),
5280
        'auth_' => array('auth'),
5281
        'block_' => array('blocks'),
5282
        'datafield_' => array('mod/data/field'),
5283
        'datapreset_' => array('mod/data/preset'),
5284
        'enrol_' => array('enrol'),
5285
        'filter_' => array('filter'),
5286
        'format_' => array('course/format'),
5287
        'qtype_' => array('question/type'),
5288
        'report_' => array($CFG->admin.'/report', 'course/report', 'mod/quiz/report'),
5289
        'resource_' => array('mod/resource/type'),
5290
        'gradereport_' => array('grade/report'),
5291
        'gradeimport_' => array('grade/import'),
5292
        'gradeexport_' => array('grade/export'),
5293
        'qformat_' => array('question/format'),
5294
        'profilefield_' => array('user/profile/field'),
5295
        '' => array('mod')
5296
    );
5297
}
5298

    
5299
/**
5300
 * Returns a localized string.
5301
 *
5302
 * Returns the translated string specified by $identifier as
5303
 * for $module.  Uses the same format files as STphp.
5304
 * $a is an object, string or number that can be used
5305
 * within translation strings
5306
 *
5307
 * eg "hello \$a->firstname \$a->lastname"
5308
 * or "hello \$a"
5309
 *
5310
 * If you would like to directly echo the localized string use
5311
 * the function {@link print_string()}
5312
 *
5313
 * Example usage of this function involves finding the string you would
5314
 * like a local equivalent of and using its identifier and module information
5315
 * to retrive it.<br/>
5316
 * If you open moodle/lang/en/moodle.php and look near line 1031
5317
 * you will find a string to prompt a user for their word for student
5318
 * <code>
5319
 * $string['wordforstudent'] = 'Your word for Student';
5320
 * </code>
5321
 * So if you want to display the string 'Your word for student'
5322
 * in any language that supports it on your site
5323
 * you just need to use the identifier 'wordforstudent'
5324
 * <code>
5325
 * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
5326
or
5327
 * </code>
5328
 * If the string you want is in another file you'd take a slightly
5329
 * different approach. Looking in moodle/lang/en/calendar.php you find
5330
 * around line 75:
5331
 * <code>
5332
 * $string['typecourse'] = 'Course event';
5333
 * </code>
5334
 * If you want to display the string "Course event" in any language
5335
 * supported you would use the identifier 'typecourse' and the module 'calendar'
5336
 * (because it is in the file calendar.php):
5337
 * <code>
5338
 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
5339
 * </code>
5340
 *
5341
 * As a last resort, should the identifier fail to map to a string
5342
 * the returned string will be [[ $identifier ]]
5343
 *
5344
 * @uses $CFG
5345
 * @param string $identifier The key identifier for the localized string
5346
 * @param string $module The module where the key identifier is stored, usually expressed as the filename in the language pack without the .php on the end but can also be written as mod/forum or grade/export/xls.  If none is specified then moodle.php is used.
5347
 * @param mixed $a An object, string or number that can be used
5348
 * within translation strings
5349
 * @param array $extralocations An array of strings with other locations to look for string files
5350
 * @return string The localized string.
5351
 */
5352
function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
5353

    
5354
    global $CFG;
5355

    
5356
/// originally these special strings were stored in moodle.php now we are only in langconfig.php
5357
    $langconfigstrs = array('alphabet', 'backupnameformat', 'decsep', 'firstdayofweek', 'listsep', 'locale',
5358
                            'localewin', 'localewincharset', 'oldcharset',
5359
                            'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
5360
                            'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
5361
                            'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
5362
                            'thischarset', 'thisdirection', 'thislanguage', 'strftimedatetimeshort', 'thousandssep');
5363

    
5364
    $filetocheck = 'langconfig.php';
5365
    $defaultlang = 'en_utf8';
5366
    if (in_array($identifier, $langconfigstrs)) {
5367
        $module = 'langconfig';  //This strings are under langconfig.php for 1.6 lang packs
5368
    }
5369

    
5370
    $lang = current_language();
5371

    
5372
    if ($module == '') {
5373
        $module = 'moodle';
5374
    }
5375

    
5376
/// If the "module" is actually a pathname, then automatically derive the proper module name
5377
    if (strpos($module, '/') !== false) {
5378
        $modulepath = split('/', $module);
5379

    
5380
        switch ($modulepath[0]) {
5381

    
5382
            case 'mod':
5383
                $module = $modulepath[1];
5384
            break;
5385

    
5386
            case 'blocks':
5387
            case 'block':
5388
                $module = 'block_'.$modulepath[1];
5389
            break;
5390

    
5391
            case 'enrol':
5392
                $module = 'enrol_'.$modulepath[1];
5393
            break;
5394

    
5395
            case 'format':
5396
                $module = 'format_'.$modulepath[1];
5397
            break;
5398

    
5399
            case 'grade':
5400
                $module = 'grade'.$modulepath[1].'_'.$modulepath[2];
5401
            break;
5402
        }
5403
    }
5404

    
5405
/// if $a happens to have % in it, double it so sprintf() doesn't break
5406
    if ($a) {
5407
        $a = clean_getstring_data( $a );
5408
    }
5409

    
5410
/// Define the two or three major locations of language strings for this module
5411
    $locations = array();
5412

    
5413
    if (!empty($extralocations)) {   // Calling code has a good idea where to look
5414
        if (is_array($extralocations)) {
5415
            $locations += $extralocations;
5416
        } else if (is_string($extralocations)) {
5417
            $locations[] = $extralocations;
5418
        } else {
5419
            debugging('Bad lang path provided');
5420
        }
5421
    }
5422

    
5423
    if (isset($CFG->running_installer)) {
5424
        $module = 'installer';
5425
        $filetocheck = 'installer.php';
5426
        $locations[] = $CFG->dirroot.'/install/lang/';
5427
        $locations[] = $CFG->dataroot.'/lang/';
5428
        $locations[] = $CFG->dirroot.'/lang/';
5429
        $defaultlang = 'en_utf8';
5430
    } else {
5431
        $locations[] = $CFG->dataroot.'/lang/';
5432
        $locations[] = $CFG->dirroot.'/lang/';
5433
    }
5434

    
5435
/// Add extra places to look for strings for particular plugin types.
5436
    $rules = places_to_search_for_lang_strings();
5437
    $exceptions = $rules['__exceptions'];
5438
    unset($rules['__exceptions']);
5439

    
5440
    if (!in_array($module, $exceptions)) {
5441
        $dividerpos = strpos($module, '_');
5442
        if ($dividerpos === false) {
5443
            $type = '';
5444
            $plugin = $module;
5445
        } else {
5446
            $type = substr($module, 0, $dividerpos + 1);
5447
            $plugin = substr($module, $dividerpos + 1);
5448
        }
5449
        if ($module == 'local') {
5450
            $locations[] = $CFG->dirroot . '/local/lang/';
5451
        } if (!empty($rules[$type])) {
5452
            foreach ($rules[$type] as $location) {
5453
                $locations[] = $CFG->dirroot . "/$location/$plugin/lang/";
5454
            }
5455
        }
5456
    }
5457

    
5458
/// First check all the normal locations for the string in the current language
5459
    $resultstring = '';
5460
    foreach ($locations as $location) {
5461
        $locallangfile = $location.$lang.'_local'.'/'.$module.'.php';    //first, see if there's a local file
5462
        if (file_exists($locallangfile)) {
5463
            if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5464
                if (eval($result) === FALSE) {
5465
                    trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5466
                }
5467
                return $resultstring;
5468
            }
5469
        }
5470
        //if local directory not found, or particular string does not exist in local direcotry
5471
        $langfile = $location.$lang.'/'.$module.'.php';
5472
        if (file_exists($langfile)) {
5473
            if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5474
                if (eval($result) === FALSE) {
5475
                    trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5476
                }
5477
                return $resultstring;
5478
            }
5479
       }
5480
    }
5481

    
5482
/// If the preferred language was English (utf8) we can abort now
5483
/// saving some checks beacuse it's the only "root" lang
5484
    if ($lang == 'en_utf8') {
5485
        return '[['. $identifier .']]';
5486
    }
5487

    
5488
/// Is a parent language defined?  If so, try to find this string in a parent language file
5489

    
5490
    foreach ($locations as $location) {
5491
        $langfile = $location.$lang.'/'.$filetocheck;
5492
        if (file_exists($langfile)) {
5493
            if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
5494
                if (eval($result) === FALSE) {
5495
                    trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5496
                }
5497
                if (!empty($parentlang)) {   // found it!
5498

    
5499
                    //first, see if there's a local file for parent
5500
                    $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
5501
                    if (file_exists($locallangfile)) {
5502
                        if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5503
                            if (eval($result) === FALSE) {
5504
                                trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5505
                            }
5506
                            return $resultstring;
5507
                        }
5508
                    }
5509

    
5510
                    //if local directory not found, or particular string does not exist in local direcotry
5511
                    $langfile = $location.$parentlang.'/'.$module.'.php';
5512
                    if (file_exists($langfile)) {
5513
                        if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5514
                            eval($result);
5515
                            return $resultstring;
5516
                        }
5517
                    }
5518
                }
5519
            }
5520
        }
5521
    }
5522

    
5523
/// Our only remaining option is to try English
5524

    
5525
    foreach ($locations as $location) {
5526
        $locallangfile = $location.$defaultlang.'_local/'.$module.'.php';    //first, see if there's a local file
5527
        if (file_exists($locallangfile)) {
5528
            if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5529
                eval($result);
5530
                return $resultstring;
5531
            }
5532
        }
5533

    
5534
        //if local_en not found, or string not found in local_en
5535
        $langfile = $location.$defaultlang.'/'.$module.'.php';
5536

    
5537
        if (file_exists($langfile)) {
5538
            if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5539
                eval($result);
5540
                return $resultstring;
5541
            }
5542
        }
5543
    }
5544

    
5545
/// And, because under 1.6 en is defined as en_utf8 child, me must try
5546
/// if it hasn't been queried before.
5547
    if ($defaultlang  == 'en') {
5548
        $defaultlang = 'en_utf8';
5549
        foreach ($locations as $location) {
5550
            $locallangfile = $location.$defaultlang.'_local/'.$module.'.php';    //first, see if there's a local file
5551
            if (file_exists($locallangfile)) {
5552
                if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5553
                    eval($result);
5554
                    return $resultstring;
5555
                }
5556
            }
5557

    
5558
            //if local_en not found, or string not found in local_en
5559
            $langfile = $location.$defaultlang.'/'.$module.'.php';
5560

    
5561
            if (file_exists($langfile)) {
5562
                if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5563
                    eval($result);
5564
                    return $resultstring;
5565
                }
5566
            }
5567
        }
5568
    }
5569

    
5570
    return '[['.$identifier.']]';  // Last resort
5571
}
5572

    
5573
/**
5574
 * This function is only used from {@link get_string()}.
5575
 *
5576
 * @internal Only used from get_string, not meant to be public API
5577
 * @param string $identifier ?
5578
 * @param string $langfile ?
5579
 * @param string $destination ?
5580
 * @return string|false ?
5581
 * @staticvar array $strings Localized strings
5582
 * @access private
5583
 * @todo Finish documenting this function.
5584
 */
5585
function get_string_from_file($identifier, $langfile, $destination) {
5586

    
5587
    static $strings;    // Keep the strings cached in memory.
5588

    
5589
    if (empty($strings[$langfile])) {
5590
        $string = array();
5591
        include ($langfile);
5592
        $strings[$langfile] = $string;
5593
    } else {
5594
        $string = &$strings[$langfile];
5595
    }
5596

    
5597
    if (!isset ($string[$identifier])) {
5598
        return false;
5599
    }
5600

    
5601
    return $destination .'= sprintf("'. $string[$identifier] .'");';
5602
}
5603

    
5604
/**
5605
 * Converts an array of strings to their localized value.
5606
 *
5607
 * @param array $array An array of strings
5608
 * @param string $module The language module that these strings can be found in.
5609
 * @return string
5610
 */
5611
function get_strings($array, $module='') {
5612

    
5613
   $string = NULL;
5614
   foreach ($array as $item) {
5615
       $string->$item = get_string($item, $module);
5616
   }
5617
   return $string;
5618
}
5619

    
5620
/**
5621
 * Returns a list of language codes and their full names
5622
 * hides the _local files from everyone.
5623
 * @param bool refreshcache force refreshing of lang cache
5624
 * @param bool returnall ignore langlist, return all languages available
5625
 * @return array An associative array with contents in the form of LanguageCode => LanguageName
5626
 */
5627
function get_list_of_languages($refreshcache=false, $returnall=false) {
5628

    
5629
    global $CFG;
5630

    
5631
    $languages = array();
5632

    
5633
    $filetocheck = 'langconfig.php';
5634

    
5635
    if (!$refreshcache && !$returnall && !empty($CFG->langcache) && file_exists($CFG->dataroot .'/cache/languages')) {
5636
/// read available langs from cache
5637

    
5638
        $lines = file($CFG->dataroot .'/cache/languages');
5639
        foreach ($lines as $line) {
5640
            $line = trim($line);
5641
            if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
5642
                $languages[$matches[1]] = $matches[2];
5643
            }
5644
        }
5645
        unset($lines); unset($line); unset($matches);
5646
        return $languages;
5647
    }
5648

    
5649
    if (!$returnall && !empty($CFG->langlist)) {
5650
/// return only languages allowed in langlist admin setting
5651

    
5652
        $langlist = explode(',', $CFG->langlist);
5653
        // fix short lang names first - non existing langs are skipped anyway...
5654
        foreach ($langlist as $lang) {
5655
            if (strpos($lang, '_utf8') === false) {
5656
                $langlist[] = $lang.'_utf8';
5657
            }
5658
        }
5659
        // find existing langs from langlist
5660
        foreach ($langlist as $lang) {
5661
            $lang = trim($lang);   //Just trim spaces to be a bit more permissive
5662
            if (strstr($lang, '_local')!==false) {
5663
                continue;
5664
            }
5665
            if (substr($lang, -5) == '_utf8') {   //Remove the _utf8 suffix from the lang to show
5666
                $shortlang = substr($lang, 0, -5);
5667
            } else {
5668
                $shortlang = $lang;
5669
            }
5670
        /// Search under dirroot/lang
5671
            if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5672
                include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5673
                if (!empty($string['thislanguage'])) {
5674
                    $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5675
                }
5676
                unset($string);
5677
            }
5678
        /// And moodledata/lang
5679
            if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5680
                include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5681
                if (!empty($string['thislanguage'])) {
5682
                    $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5683
                }
5684
                unset($string);
5685
            }
5686
        }
5687

    
5688
    } else {
5689
/// return all languages available in system
5690
    /// Fetch langs from moodle/lang directory
5691
        $langdirs = get_list_of_plugins('lang');
5692
    /// Fetch langs from moodledata/lang directory
5693
        $langdirs2 = get_list_of_plugins('lang', '', $CFG->dataroot);
5694
    /// Merge both lists of langs
5695
        $langdirs = array_merge($langdirs, $langdirs2);
5696
    /// Sort all
5697
        asort($langdirs);
5698
    /// Get some info from each lang (first from moodledata, then from moodle)
5699
        foreach ($langdirs as $lang) {
5700
            if (strstr($lang, '_local')!==false) {
5701
                continue;
5702
            }
5703
            if (substr($lang, -5) == '_utf8') {   //Remove the _utf8 suffix from the lang to show
5704
                $shortlang = substr($lang, 0, -5);
5705
            } else {
5706
                $shortlang = $lang;
5707
            }
5708
        /// Search under moodledata/lang
5709
            if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5710
                include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5711
                if (!empty($string['thislanguage'])) {
5712
                    $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5713
                }
5714
                unset($string);
5715
            }
5716
        /// And dirroot/lang
5717
            if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5718
                include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5719
                if (!empty($string['thislanguage'])) {
5720
                    $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5721
                }
5722
                unset($string);
5723
            }
5724
        }
5725
    }
5726

    
5727
    if ($refreshcache && !empty($CFG->langcache)) {
5728
        if ($returnall) {
5729
            // we have a list of all langs only, just delete old cache
5730
            @unlink($CFG->dataroot.'/cache/languages');
5731

    
5732
        } else {
5733
            // store the list of allowed languages
5734
            if ($file = fopen($CFG->dataroot .'/cache/languages', 'w')) {
5735
                foreach ($languages as $key => $value) {
5736
                    fwrite($file, "$key $value\n");
5737
                }
5738
                fclose($file);
5739
            }
5740
        }
5741
    }
5742

    
5743
    return $languages;
5744
}
5745

    
5746
/**
5747
 * Returns a list of charset codes. It's hardcoded, so they should be added manually
5748
 * (cheking that such charset is supported by the texlib library!)
5749
 *
5750
 * @return array And associative array with contents in the form of charset => charset
5751
 */
5752
function get_list_of_charsets() {
5753

    
5754
    $charsets = array(
5755
        'EUC-JP'     => 'EUC-JP',
5756
        'ISO-2022-JP'=> 'ISO-2022-JP',
5757
        'ISO-8859-1' => 'ISO-8859-1',
5758
        'SHIFT-JIS'  => 'SHIFT-JIS',
5759
        'GB2312'     => 'GB2312',
5760
        'GB18030'    => 'GB18030', // gb18030 not supported by typo and mbstring
5761
        'UTF-8'      => 'UTF-8');
5762

    
5763
    asort($charsets);
5764

    
5765
    return $charsets;
5766
}
5767

    
5768
/**
5769
 * For internal use only.
5770
 * @return array with two elements, the path to use and the name of the lang.
5771
 */
5772
function get_list_of_countries_language() {
5773
        global $CFG;
5774

    
5775
        $lang = current_language();
5776
    if (is_readable($CFG->dataroot.'/lang/'. $lang .'/countries.php')) {
5777
        return array($CFG->dataroot, $lang);
5778
    }
5779
    if (is_readable($CFG->dirroot .'/lang/'. $lang .'/countries.php')) {
5780
        return array($CFG->dirroot , $lang);
5781
    }
5782

    
5783
    if ($lang == 'en_utf8') {
5784
            return;
5785
    }
5786

    
5787
    $parentlang = get_string('parentlanguage');
5788
    if (substr($parentlang, 0, 1) != '[') {
5789
            if (is_readable($CFG->dataroot.'/lang/'. $parentlang .'/countries.php')) {
5790
                return array($CFG->dataroot, $parentlang);
5791
            }
5792
            if (is_readable($CFG->dirroot .'/lang/'. $parentlang .'/countries.php')) {
5793
                return array($CFG->dirroot , $parentlang);
5794
            }
5795

    
5796
            if ($parentlang == 'en_utf8') {
5797
                return;
5798
            }
5799
    }
5800

    
5801
    if (is_readable($CFG->dataroot.'/lang/en_utf8/countries.php')) {
5802
        return array($CFG->dataroot, 'en_utf8');
5803
    }
5804
    if (is_readable($CFG->dirroot .'/lang/en_utf8/countries.php')) {
5805
        return array($CFG->dirroot , 'en_utf8');
5806
    }
5807

    
5808
    return array(null, null);
5809
}
5810

    
5811
/**
5812
 * Returns a list of country names in the current language
5813
 *
5814
 * @uses $CFG
5815
 * @uses $USER
5816
 * @return array
5817
 */
5818
function get_list_of_countries() {
5819
    global $CFG;
5820

    
5821
    list($path, $lang) = get_list_of_countries_language();
5822

    
5823
    if (empty($path)) {
5824
            print_error('countriesphpempty', '', '', $lang);
5825
    }
5826

    
5827
    // Load all the strings into $string.
5828
    include($path . '/lang/' . $lang . '/countries.php');
5829

    
5830
    // See if there are local overrides to countries.php.
5831
    // If so, override those elements of $string.
5832
    if (is_readable($CFG->dirroot .'/lang/' . $lang . '_local/countries.php')) {
5833
        include($CFG->dirroot .'/lang/' . $lang . '_local/countries.php');
5834
    }
5835
    if (is_readable($CFG->dataroot.'/lang/' . $lang . '_local/countries.php')) {
5836
        include($CFG->dataroot.'/lang/' . $lang . '_local/countries.php');
5837
    }
5838

    
5839
    if (empty($string)) {
5840
        print_error('countriesphpempty', '', '', $lang);
5841
    }
5842

    
5843
    uasort($string, 'strcoll');
5844
    return $string;
5845
}
5846

    
5847
/**
5848
 * Returns a list of valid and compatible themes
5849
 *
5850
 * @uses $CFG
5851
 * @return array
5852
 */
5853
function get_list_of_themes() {
5854

    
5855
    global $CFG;
5856

    
5857
    $themes = array();
5858

    
5859
    if (!empty($CFG->themelist)) {       // use admin's list of themes
5860
        $themelist = explode(',', $CFG->themelist);
5861
    } else {
5862
        $themelist = get_list_of_plugins("theme");
5863
    }
5864

    
5865
    foreach ($themelist as $key => $theme) {
5866
        if (!file_exists("$CFG->themedir/$theme/config.php")) {   // bad folder
5867
            continue;
5868
        }
5869
        $THEME = new object();    // Note this is not the global one!!  :-)
5870
        include("$CFG->themedir/$theme/config.php");
5871
        if (!isset($THEME->sheets)) {   // Not a valid 1.5 theme
5872
            continue;
5873
        }
5874
        $themes[$theme] = $theme;
5875
    }
5876
    asort($themes);
5877

    
5878
    return $themes;
5879
}
5880

    
5881

    
5882
/**
5883
 * Returns a list of picture names in the current or specified language
5884
 *
5885
 * @uses $CFG
5886
 * @return array
5887
 */
5888
function get_list_of_pixnames($lang = '') {
5889
    global $CFG;
5890

    
5891
    if (empty($lang)) {
5892
        $lang = current_language();
5893
    }
5894

    
5895
    $string = array();
5896

    
5897
    $path = $CFG->dirroot .'/lang/en_utf8/pix.php'; // always exists
5898

    
5899
    if (file_exists($CFG->dataroot .'/lang/'. $lang .'_local/pix.php')) {
5900
        $path = $CFG->dataroot .'/lang/'. $lang .'_local/pix.php';
5901

    
5902
    } else if (file_exists($CFG->dirroot .'/lang/'. $lang .'/pix.php')) {
5903
        $path = $CFG->dirroot .'/lang/'. $lang .'/pix.php';
5904

    
5905
    } else if (file_exists($CFG->dataroot .'/lang/'. $lang .'/pix.php')) {
5906
        $path = $CFG->dataroot .'/lang/'. $lang .'/pix.php';
5907

    
5908
    } else if ($parentlang = get_string('parentlanguage') and $parentlang != '[[parentlanguage]]') {
5909
        return get_list_of_pixnames($parentlang); //return pixnames from parent language instead
5910
    }
5911

    
5912
    include($path);
5913

    
5914
    return $string;
5915
}
5916

    
5917
/**
5918
 * Returns a list of timezones in the current language
5919
 *
5920
 * @uses $CFG
5921
 * @return array
5922
 */
5923
function get_list_of_timezones() {
5924
    global $CFG;
5925

    
5926
    static $timezones;
5927

    
5928
    if (!empty($timezones)) {    // This function has been called recently
5929
        return $timezones;
5930
    }
5931

    
5932
    $timezones = array();
5933

    
5934
    if ($rawtimezones = get_records_sql('SELECT MAX(id), name FROM '.$CFG->prefix.'timezone GROUP BY name')) {
5935
        foreach($rawtimezones as $timezone) {
5936
            if (!empty($timezone->name)) {
5937
                $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
5938
                if (substr($timezones[$timezone->name], 0, 1) == '[') {  // No translation found
5939
                    $timezones[$timezone->name] = $timezone->name;
5940
                }
5941
            }
5942
        }
5943
    }
5944

    
5945
    asort($timezones);
5946

    
5947
    for ($i = -13; $i <= 13; $i += .5) {
5948
        $tzstring = 'UTC';
5949
        if ($i < 0) {
5950
            $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
5951
        } else if ($i > 0) {
5952
            $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
5953
        } else {
5954
            $timezones[sprintf("%.1f", $i)] = $tzstring;
5955
        }
5956
    }
5957

    
5958
    return $timezones;
5959
}
5960

    
5961
/**
5962
 * Returns a list of currencies in the current language
5963
 *
5964
 * @uses $CFG
5965
 * @uses $USER
5966
 * @return array
5967
 */
5968
function get_list_of_currencies() {
5969
    global $CFG, $USER;
5970

    
5971
    $lang = current_language();
5972

    
5973
    if (!file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5974
        if ($parentlang = get_string('parentlanguage')) {
5975
            if (file_exists($CFG->dataroot .'/lang/'. $parentlang .'/currencies.php')) {
5976
                $lang = $parentlang;
5977
            } else {
5978
                $lang = 'en_utf8';  // currencies.php must exist in this pack
5979
            }
5980
        } else {
5981
            $lang = 'en_utf8';  // currencies.php must exist in this pack
5982
        }
5983
    }
5984

    
5985
    if (file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5986
        include_once($CFG->dataroot .'/lang/'. $lang .'/currencies.php');
5987
    } else {    //if en_utf8 is not installed in dataroot
5988
        include_once($CFG->dirroot .'/lang/'. $lang .'/currencies.php');
5989
    }
5990

    
5991
    if (!empty($string)) {
5992
        asort($string);
5993
    }
5994

    
5995
    return $string;
5996
}
5997

    
5998

    
5999
/// ENCRYPTION  ////////////////////////////////////////////////
6000

    
6001
/**
6002
 * rc4encrypt
6003
 *
6004
 * @param string $data ?
6005
 * @return string
6006
 * @todo Finish documenting this function
6007
 */
6008
function rc4encrypt($data) {
6009
    $password = 'nfgjeingjk';
6010
    return endecrypt($password, $data, '');
6011
}
6012

    
6013
/**
6014
 * rc4decrypt
6015
 *
6016
 * @param string $data ?
6017
 * @return string
6018
 * @todo Finish documenting this function
6019
 */
6020
function rc4decrypt($data) {
6021
    $password = 'nfgjeingjk';
6022
    return endecrypt($password, $data, 'de');
6023
}
6024

    
6025
/**
6026
 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6027
 *
6028
 * @param string $pwd ?
6029
 * @param string $data ?
6030
 * @param string $case ?
6031
 * @return string
6032
 * @todo Finish documenting this function
6033
 */
6034
function endecrypt ($pwd, $data, $case) {
6035

    
6036
    if ($case == 'de') {
6037
        $data = urldecode($data);
6038
    }
6039

    
6040
    $key[] = '';
6041
    $box[] = '';
6042
    $temp_swap = '';
6043
    $pwd_length = 0;
6044

    
6045
    $pwd_length = strlen($pwd);
6046

    
6047
    for ($i = 0; $i <= 255; $i++) {
6048
        $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
6049
        $box[$i] = $i;
6050
    }
6051

    
6052
    $x = 0;
6053

    
6054
    for ($i = 0; $i <= 255; $i++) {
6055
        $x = ($x + $box[$i] + $key[$i]) % 256;
6056
        $temp_swap = $box[$i];
6057
        $box[$i] = $box[$x];
6058
        $box[$x] = $temp_swap;
6059
    }
6060

    
6061
    $temp = '';
6062
    $k = '';
6063

    
6064
    $cipherby = '';
6065
    $cipher = '';
6066

    
6067
    $a = 0;
6068
    $j = 0;
6069

    
6070
    for ($i = 0; $i < strlen($data); $i++) {
6071
        $a = ($a + 1) % 256;
6072
        $j = ($j + $box[$a]) % 256;
6073
        $temp = $box[$a];
6074
        $box[$a] = $box[$j];
6075
        $box[$j] = $temp;
6076
        $k = $box[(($box[$a] + $box[$j]) % 256)];
6077
        $cipherby = ord(substr($data, $i, 1)) ^ $k;
6078
        $cipher .= chr($cipherby);
6079
    }
6080

    
6081
    if ($case == 'de') {
6082
        $cipher = urldecode(urlencode($cipher));
6083
    } else {
6084
        $cipher = urlencode($cipher);
6085
    }
6086

    
6087
    return $cipher;
6088
}
6089

    
6090

    
6091
/// CALENDAR MANAGEMENT  ////////////////////////////////////////////////////////////////
6092

    
6093

    
6094
/**
6095
 * Call this function to add an event to the calendar table
6096
 *  and to call any calendar plugins
6097
 *
6098
 * @uses $CFG
6099
 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field. The object event should include the following:
6100
 *  <ul>
6101
 *    <li><b>$event->name</b> - Name for the event
6102
 *    <li><b>$event->description</b> - Description of the event (defaults to '')
6103
 *    <li><b>$event->format</b> - Format for the description (using formatting types defined at the top of weblib.php)
6104
 *    <li><b>$event->courseid</b> - The id of the course this event belongs to (0 = all courses)
6105
 *    <li><b>$event->groupid</b> - The id of the group this event belongs to (0 = no group)
6106
 *    <li><b>$event->userid</b> - The id of the user this event belongs to (0 = no user)
6107
 *    <li><b>$event->modulename</b> - Name of the module that creates this event
6108
 *    <li><b>$event->instance</b> - Instance of the module that owns this event
6109
 *    <li><b>$event->eventtype</b> - The type info together with the module info could
6110
 *             be used by calendar plugins to decide how to display event
6111
 *    <li><b>$event->timestart</b>- Timestamp for start of event
6112
 *    <li><b>$event->timeduration</b> - Duration (defaults to zero)
6113
 *    <li><b>$event->visible</b> - 0 if the event should be hidden (e.g. because the activity that created it is hidden)
6114
 *  </ul>
6115
 * @return int The id number of the resulting record
6116
 */
6117
 function add_event($event) {
6118

    
6119
    global $CFG;
6120

    
6121
    $event->timemodified = time();
6122

    
6123
    if (!$event->id = insert_record('event', $event)) {
6124
        return false;
6125
    }
6126

    
6127
    if (!empty($CFG->calendar)) { // call the add_event function of the selected calendar
6128
        if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6129
            include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6130
            $calendar_add_event = $CFG->calendar.'_add_event';
6131
            if (function_exists($calendar_add_event)) {
6132
                $calendar_add_event($event);
6133
            }
6134
        }
6135
    }
6136

    
6137
    return $event->id;
6138
}
6139

    
6140
/**
6141
 * Call this function to update an event in the calendar table
6142
 * the event will be identified by the id field of the $event object.
6143
 *
6144
 * @uses $CFG
6145
 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6146
 * @return bool
6147
 */
6148
function update_event($event) {
6149

    
6150
    global $CFG;
6151

    
6152
    $event->timemodified = time();
6153

    
6154
    if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6155
        if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6156
            include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6157
            $calendar_update_event = $CFG->calendar.'_update_event';
6158
            if (function_exists($calendar_update_event)) {
6159
                $calendar_update_event($event);
6160
            }
6161
        }
6162
    }
6163
    return update_record('event', $event);
6164
}
6165

    
6166
/**
6167
 * Call this function to delete the event with id $id from calendar table.
6168
 *
6169
 * @uses $CFG
6170
 * @param int $id The id of an event from the 'calendar' table.
6171
 * @return array An associative array with the results from the SQL call.
6172
 * @todo Verify return type
6173
 */
6174
function delete_event($id) {
6175

    
6176
    global $CFG;
6177

    
6178
    if (!empty($CFG->calendar)) { // call the delete_event function of the selected calendar
6179
        if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6180
            include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6181
            $calendar_delete_event = $CFG->calendar.'_delete_event';
6182
            if (function_exists($calendar_delete_event)) {
6183
                $calendar_delete_event($id);
6184
            }
6185
        }
6186
    }
6187
    return delete_records('event', 'id', $id);
6188
}
6189

    
6190
/**
6191
 * Call this function to hide an event in the calendar table
6192
 * the event will be identified by the id field of the $event object.
6193
 *
6194
 * @uses $CFG
6195
 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6196
 * @return array An associative array with the results from the SQL call.
6197
 * @todo Verify return type
6198
 */
6199
function hide_event($event) {
6200
    global $CFG;
6201

    
6202
    if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6203
        if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6204
            include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6205
            $calendar_hide_event = $CFG->calendar.'_hide_event';
6206
            if (function_exists($calendar_hide_event)) {
6207
                $calendar_hide_event($event);
6208
            }
6209
        }
6210
    }
6211
    return set_field('event', 'visible', 0, 'id', $event->id);
6212
}
6213

    
6214
/**
6215
 * Call this function to unhide an event in the calendar table
6216
 * the event will be identified by the id field of the $event object.
6217
 *
6218
 * @uses $CFG
6219
 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6220
 * @return array An associative array with the results from the SQL call.
6221
 * @todo Verify return type
6222
 */
6223
function show_event($event) {
6224
    global $CFG;
6225

    
6226
    if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6227
        if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6228
            include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6229
            $calendar_show_event = $CFG->calendar.'_show_event';
6230
            if (function_exists($calendar_show_event)) {
6231
                $calendar_show_event($event);
6232
            }
6233
        }
6234
    }
6235
    return set_field('event', 'visible', 1, 'id', $event->id);
6236
}
6237

    
6238

    
6239
/// ENVIRONMENT CHECKING  ////////////////////////////////////////////////////////////
6240

    
6241
/**
6242
 * Lists plugin directories within some directory
6243
 *
6244
 * @uses $CFG
6245
 * @param string $plugin dir under we'll look for plugins (defaults to 'mod')
6246
 * @param string $exclude dir name to exclude from the list (defaults to none)
6247
 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
6248
 * @return array of plugins found under the requested parameters
6249
 */
6250
function get_list_of_plugins($plugin='mod', $exclude='', $basedir='') {
6251

    
6252
    global $CFG;
6253

    
6254
    $plugins = array();
6255

    
6256
    if (empty($basedir)) {
6257

    
6258
        # This switch allows us to use the appropiate theme directory - and potentialy alternatives for other plugins
6259
        switch ($plugin) {
6260
        case "theme":
6261
            $basedir = $CFG->themedir;
6262
            break;
6263

    
6264
        default:
6265
            $basedir = $CFG->dirroot .'/'. $plugin;
6266
        }
6267

    
6268
    } else {
6269
        $basedir = $basedir .'/'. $plugin;
6270
    }
6271

    
6272
    if (file_exists($basedir) && filetype($basedir) == 'dir') {
6273
        $dirhandle = opendir($basedir);
6274
        while (false !== ($dir = readdir($dirhandle))) {
6275
            $firstchar = substr($dir, 0, 1);
6276
            if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == 'simpletest' or $dir == $exclude) {
6277
                continue;
6278
            }
6279
            if (filetype($basedir .'/'. $dir) != 'dir') {
6280
                continue;
6281
            }
6282
            $plugins[] = $dir;
6283
        }
6284
        closedir($dirhandle);
6285
    }
6286
    if ($plugins) {
6287
        asort($plugins);
6288
    }
6289
    return $plugins;
6290
}
6291

    
6292
/**
6293
 * Returns true if the current version of PHP is greater that the specified one.
6294
 *
6295
 * @param string $version The version of php being tested.
6296
 * @return bool
6297
 */
6298
function check_php_version($version='4.1.0') {
6299
    return (version_compare(phpversion(), $version) >= 0);
6300
}
6301

    
6302
/**
6303
 * Checks to see if is the browser operating system matches the specified
6304
 * brand.
6305
 *
6306
 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
6307
 *
6308
 * @uses $_SERVER
6309
 * @param string $brand The operating system identifier being tested
6310
 * @return bool true if the given brand below to the detected operating system
6311
 */
6312
 function check_browser_operating_system($brand) {
6313
    if (empty($_SERVER['HTTP_USER_AGENT'])) {
6314
        return false;
6315
    }
6316

    
6317
    if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
6318
        return true;
6319
    }
6320

    
6321
    return false;
6322
 }
6323

    
6324
/**
6325
 * Checks to see if is a browser matches the specified
6326
 * brand and is equal or better version.
6327
 *
6328
 * @uses $_SERVER
6329
 * @param string $brand The browser identifier being tested
6330
 * @param int $version The version of the browser
6331
 * @return bool true if the given version is below that of the detected browser
6332
 */
6333
 function check_browser_version($brand='MSIE', $version=5.5) {
6334
    if (empty($_SERVER['HTTP_USER_AGENT'])) {
6335
        return false;
6336
    }
6337

    
6338
    $agent = $_SERVER['HTTP_USER_AGENT'];
6339

    
6340
    switch ($brand) {
6341

    
6342
      case 'Camino':   /// Mozilla Firefox browsers
6343

    
6344
              if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
6345
                  if (version_compare($match[1], $version) >= 0) {
6346
                      return true;
6347
                  }
6348
              }
6349
              break;
6350

    
6351

    
6352
      case 'Firefox':   /// Mozilla Firefox browsers
6353

    
6354
          if (preg_match("/Firefox\/([0-9\.]+)/i", $agent, $match)) {
6355
              if (version_compare($match[1], $version) >= 0) {
6356
                  return true;
6357
              }
6358
          }
6359
          break;
6360

    
6361

    
6362
      case 'Gecko':   /// Gecko based browsers
6363

    
6364
          if (substr_count($agent, 'Camino')) {
6365
              // MacOS X Camino support
6366
              $version = 20041110;
6367
          }
6368

    
6369
          // the proper string - Gecko/CCYYMMDD Vendor/Version
6370
          // Faster version and work-a-round No IDN problem.
6371
          if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
6372
              if ($match[1] > $version) {
6373
                      return true;
6374
                  }
6375
              }
6376
          break;
6377

    
6378

    
6379
      case 'MSIE':   /// Internet Explorer
6380

    
6381
          if (strpos($agent, 'Opera')) {     // Reject Opera
6382
              return false;
6383
          }
6384
          $string = explode(';', $agent);
6385
          if (!isset($string[1])) {
6386
              return false;
6387
          }
6388
          $string = explode(' ', trim($string[1]));
6389
          if (!isset($string[0]) and !isset($string[1])) {
6390
              return false;
6391
          }
6392
          if ($string[0] == $brand and (float)$string[1] >= $version ) {
6393
              return true;
6394
          }
6395
          break;
6396

    
6397
      case 'Opera':  /// Opera
6398

    
6399
          if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
6400
              if (version_compare($match[1], $version) >= 0) {
6401
                  return true;
6402
              }
6403
          }
6404
          break;
6405

    
6406
      case 'Safari':  /// Safari
6407
          // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SimbianOS
6408
          if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
6409
              return false;
6410
          } elseif (strpos($agent, 'Shiira')) { // Reject Shiira
6411
              return false;
6412
          } elseif (strpos($agent, 'SimbianOS')) { // Reject SimbianOS
6413
              return false;
6414
          }
6415

    
6416
          if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
6417
              if (version_compare($match[1], $version) >= 0) {
6418
                  return true;
6419
              }
6420
          }
6421

    
6422
          break;
6423

    
6424
    }
6425

    
6426
    return false;
6427
}
6428

    
6429
/**
6430
 * Returns one or several CSS class names that match the user's browser. These can be put
6431
 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
6432
 */
6433
function get_browser_version_classes() {
6434
    $classes = '';
6435
    if (check_browser_version("MSIE", "0")) {
6436
        $classes .= 'ie ';
6437
        if (check_browser_version("MSIE", 8)) {
6438
            $classes .= 'ie8 ';
6439
        } elseif (check_browser_version("MSIE", 7)) {
6440
            $classes .= 'ie7 ';
6441
        } elseif (check_browser_version("MSIE", 6)) {
6442
            $classes .= 'ie6 ';
6443
        }
6444
    } elseif (check_browser_version("Firefox", 0) || check_browser_version("Gecko", 0) || check_browser_version("Camino", 0)) {
6445
        $classes .= 'gecko ';
6446

    
6447
        if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
6448
            $classes .= "gecko{$matches[1]}{$matches[2]} ";
6449
        }
6450

    
6451
    } elseif (check_browser_version("Safari", 0)) {
6452
        $classes .= 'safari ';
6453

    
6454
    } elseif (check_browser_version("Opera", 0)) {
6455
        $classes .= 'opera ';
6456

    
6457
    }
6458

    
6459
    return $classes;
6460
}
6461

    
6462
/**
6463
 * This function makes the return value of ini_get consistent if you are
6464
 * setting server directives through the .htaccess file in apache.
6465
 * Current behavior for value set from php.ini On = 1, Off = [blank]
6466
 * Current behavior for value set from .htaccess On = On, Off = Off
6467
 * Contributed by jdell @ unr.edu
6468
 *
6469
 * @param string $ini_get_arg ?
6470
 * @return bool
6471
 * @todo Finish documenting this function
6472
 */
6473
function ini_get_bool($ini_get_arg) {
6474
    $temp = ini_get($ini_get_arg);
6475

    
6476
    if ($temp == '1' or strtolower($temp) == 'on') {
6477
        return true;
6478
    }
6479
    return false;
6480
}
6481

    
6482
/**
6483
 * Compatibility stub to provide backward compatibility
6484
 *
6485
 * Determines if the HTML editor is enabled.
6486
 * @deprecated Use {@link can_use_html_editor()} instead.
6487
 */
6488
function can_use_richtext_editor() {
6489
    return can_use_html_editor();
6490
}
6491

    
6492
/**
6493
 * Determines if the HTML editor is enabled.
6494
 *
6495
 * This depends on site and user
6496
 * settings, as well as the current browser being used.
6497
 *
6498
 * @return string|false Returns false if editor is not being used, otherwise
6499
 * returns 'MSIE' or 'Gecko'.
6500
 */
6501
function can_use_html_editor() {
6502
    global $USER, $CFG;
6503

    
6504
    if (!empty($USER->htmleditor) and !empty($CFG->htmleditor)) {
6505
        if (check_browser_version('MSIE', 5.5)) {
6506
            return 'MSIE';
6507
        } else if (check_browser_version('Gecko', 20030516)) {
6508
            return 'Gecko';
6509
        } else if (check_browser_version('Safari', 5)) {
6510
                return 'Safari';
6511
# modification chrome
6512
        } else if (check_browser_version('Chrome', 5)) {
6513
        return 'Chrome';
6514
        }
6515
    }
6516
    return false;
6517
}
6518

    
6519
/**
6520
 * Hack to find out the GD version by parsing phpinfo output
6521
 *
6522
 * @return int GD version (1, 2, or 0)
6523
 */
6524
function check_gd_version() {
6525
    $gdversion = 0;
6526

    
6527
    if (function_exists('gd_info')){
6528
        $gd_info = gd_info();
6529
        if (substr_count($gd_info['GD Version'], '2.')) {
6530
            $gdversion = 2;
6531
        } else if (substr_count($gd_info['GD Version'], '1.')) {
6532
            $gdversion = 1;
6533
        }
6534

    
6535
    } else {
6536
        ob_start();
6537
        phpinfo(INFO_MODULES);
6538
        $phpinfo = ob_get_contents();
6539
        ob_end_clean();
6540

    
6541
        $phpinfo = explode("\n", $phpinfo);
6542

    
6543

    
6544
        foreach ($phpinfo as $text) {
6545
            $parts = explode('</td>', $text);
6546
            foreach ($parts as $key => $val) {
6547
                $parts[$key] = trim(strip_tags($val));
6548
            }
6549
            if ($parts[0] == 'GD Version') {
6550
                if (substr_count($parts[1], '2.0')) {
6551
                    $parts[1] = '2.0';
6552
                }
6553
                $gdversion = intval($parts[1]);
6554
            }
6555
        }
6556
    }
6557

    
6558
    return $gdversion;   // 1, 2 or 0
6559
}
6560

    
6561
/**
6562
 * Determine if moodle installation requires update
6563
 *
6564
 * Checks version numbers of main code and all modules to see
6565
 * if there are any mismatches
6566
 *
6567
 * @uses $CFG
6568
 * @return bool
6569
 */
6570
function moodle_needs_upgrading() {
6571
    global $CFG;
6572

    
6573
    $version = null;
6574
    include_once($CFG->dirroot .'/version.php');  # defines $version and upgrades
6575
    if ($CFG->version) {
6576
        if ($version > $CFG->version) {
6577
            return true;
6578
        }
6579
        if ($mods = get_list_of_plugins('mod')) {
6580
            foreach ($mods as $mod) {
6581
                $fullmod = $CFG->dirroot .'/mod/'. $mod;
6582
                $module = new object();
6583
                if (!is_readable($fullmod .'/version.php')) {
6584
                    notify('Module "'. $mod .'" is not readable - check permissions');
6585
                    continue;
6586
                }
6587
                include_once($fullmod .'/version.php');  # defines $module with version etc
6588
                if ($currmodule = get_record('modules', 'name', $mod)) {
6589
                    if ($module->version > $currmodule->version) {
6590
                        return true;
6591
                    }
6592
                }
6593
            }
6594
        }
6595
    } else {
6596
        return true;
6597
    }
6598
    return false;
6599
}
6600

    
6601

    
6602
/// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
6603

    
6604
/**
6605
 * Notify admin users or admin user of any failed logins (since last notification).
6606
 *
6607
 * Note that this function must be only executed from the cron script
6608
 * It uses the cache_flags system to store temporary records, deleting them
6609
 * by name before finishing
6610
 *
6611
 * @uses $CFG
6612
 * @uses $db
6613
 * @uses HOURSECS
6614
 */
6615
function notify_login_failures() {
6616
    global $CFG, $db;
6617

    
6618
    switch ($CFG->notifyloginfailures) {
6619
        case 'mainadmin' :
6620
            $recip = array(get_admin());
6621
            break;
6622
        case 'alladmins':
6623
            $recip = get_admins();
6624
            break;
6625
    }
6626

    
6627
    if (empty($CFG->lastnotifyfailure)) {
6628
        $CFG->lastnotifyfailure=0;
6629
    }
6630

    
6631
    // we need to deal with the threshold stuff first.
6632
    if (empty($CFG->notifyloginthreshold)) {
6633
        $CFG->notifyloginthreshold = 10; // default to something sensible.
6634
    }
6635

    
6636
/// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
6637
/// and insert them into the cache_flags temp table
6638
    $iprs = get_recordset_sql("SELECT ip, count(*)
6639
                                 FROM {$CFG->prefix}log
6640
                                WHERE module = 'login'
6641
                                  AND action = 'error'
6642
                                  AND time > $CFG->lastnotifyfailure
6643
                             GROUP BY ip
6644
                               HAVING count(*) >= $CFG->notifyloginthreshold");
6645
    while ($iprec = rs_fetch_next_record($iprs)) {
6646
        if (!empty($iprec->ip)) {
6647
            set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
6648
        }
6649
    }
6650
    rs_close($iprs);
6651

    
6652
/// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
6653
/// and insert them into the cache_flags temp table
6654
    $infors = get_recordset_sql("SELECT info, count(*)
6655
                                   FROM {$CFG->prefix}log
6656
                                  WHERE module = 'login'
6657
                                    AND action = 'error'
6658
                                    AND time > $CFG->lastnotifyfailure
6659
                               GROUP BY info
6660
                                 HAVING count(*) >= $CFG->notifyloginthreshold");
6661
    while ($inforec = rs_fetch_next_record($infors)) {
6662
        if (!empty($inforec->info)) {
6663
            set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
6664
        }
6665
    }
6666
    rs_close($infors);
6667

    
6668
/// Now, select all the login error logged records belonging to the ips and infos
6669
/// since lastnotifyfailure, that we have stored in the cache_flags table
6670
    $logsrs = get_recordset_sql("SELECT l.*, u.firstname, u.lastname
6671
                                   FROM {$CFG->prefix}log l
6672
                                   JOIN {$CFG->prefix}cache_flags cf ON (l.ip = cf.name)
6673
                              LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6674
                                  WHERE l.module = 'login'
6675
                                    AND l.action = 'error'
6676
                                    AND l.time > $CFG->lastnotifyfailure
6677
                                    AND cf.flagtype = 'login_failure_by_ip'
6678
                             UNION ALL
6679
                                 SELECT l.*, u.firstname, u.lastname
6680
                                   FROM {$CFG->prefix}log l
6681
                                   JOIN {$CFG->prefix}cache_flags cf ON (l.info = cf.name)
6682
                              LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6683
                                  WHERE l.module = 'login'
6684
                                    AND l.action = 'error'
6685
                                    AND l.time > $CFG->lastnotifyfailure
6686
                                    AND cf.flagtype = 'login_failure_by_info'
6687
                             ORDER BY time DESC");
6688

    
6689
/// Init some variables
6690
    $count = 0;
6691
    $messages = '';
6692
/// Iterate over the logs recordset
6693
    while ($log = rs_fetch_next_record($logsrs)) {
6694
        $log->time = userdate($log->time);
6695
        $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
6696
        $count++;
6697
    }
6698
    rs_close($logsrs);
6699

    
6700
/// If we haven't run in the last hour and
6701
/// we have something useful to report and we
6702
/// are actually supposed to be reporting to somebody
6703
    if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
6704
        $site = get_site();
6705
        $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
6706
    /// Calculate the complete body of notification (start + messages + end)
6707
        $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
6708
                (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
6709
                $messages .
6710
                "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
6711

    
6712
    /// For each destination, send mail
6713
        mtrace('Emailing admins about '. $count .' failed login attempts');
6714
        foreach ($recip as $admin) {
6715
            email_to_user($admin,get_admin(), $subject, $body);
6716
        }
6717

    
6718
    /// Update lastnotifyfailure with current time
6719
        set_config('lastnotifyfailure', time());
6720
    }
6721

    
6722
/// Finally, delete all the temp records we have created in cache_flags
6723
    delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
6724
}
6725

    
6726
/**
6727
 * moodle_setlocale
6728
 *
6729
 * @uses $CFG
6730
 * @param string $locale ?
6731
 * @todo Finish documenting this function
6732
 */
6733
function moodle_setlocale($locale='') {
6734

    
6735
    global $CFG;
6736

    
6737
    static $currentlocale = ''; // last locale caching
6738

    
6739
    $oldlocale = $currentlocale;
6740

    
6741
/// Fetch the correct locale based on ostype
6742
    if($CFG->ostype == 'WINDOWS') {
6743
        $stringtofetch = 'localewin';
6744
    } else {
6745
        $stringtofetch = 'locale';
6746
    }
6747

    
6748
/// the priority is the same as in get_string() - parameter, config, course, session, user, global language
6749
    if (!empty($locale)) {
6750
        $currentlocale = $locale;
6751
    } else if (!empty($CFG->locale)) { // override locale for all language packs
6752
        $currentlocale = $CFG->locale;
6753
    } else {
6754
        $currentlocale = get_string($stringtofetch);
6755
    }
6756

    
6757
/// do nothing if locale already set up
6758
    if ($oldlocale == $currentlocale) {
6759
        return;
6760
    }
6761

    
6762
/// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
6763
/// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
6764
/// Some day, numeric, monetary and other categories should be set too, I think. :-/
6765

    
6766
/// Get current values
6767
    $monetary= setlocale (LC_MONETARY, 0);
6768
    $numeric = setlocale (LC_NUMERIC, 0);
6769
    $ctype   = setlocale (LC_CTYPE, 0);
6770
    if ($CFG->ostype != 'WINDOWS') {
6771
        $messages= setlocale (LC_MESSAGES, 0);
6772
    }
6773
/// Set locale to all
6774
    setlocale (LC_ALL, $currentlocale);
6775
/// Set old values
6776
    setlocale (LC_MONETARY, $monetary);
6777
    setlocale (LC_NUMERIC, $numeric);
6778
    if ($CFG->ostype != 'WINDOWS') {
6779
        setlocale (LC_MESSAGES, $messages);
6780
    }
6781
    if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
6782
        setlocale (LC_CTYPE, $ctype);
6783
    }
6784
}
6785

    
6786
/**
6787
 * Converts string to lowercase using most compatible function available.
6788
 *
6789
 * @param string $string The string to convert to all lowercase characters.
6790
 * @param string $encoding The encoding on the string.
6791
 * @return string
6792
 * @todo Add examples of calling this function with/without encoding types
6793
 * @deprecated Use textlib->strtolower($text) instead.
6794
 */
6795
function moodle_strtolower ($string, $encoding='') {
6796

    
6797
    //If not specified use utf8
6798
    if (empty($encoding)) {
6799
        $encoding = 'UTF-8';
6800
    }
6801
    //Use text services
6802
    $textlib = textlib_get_instance();
6803

    
6804
    return $textlib->strtolower($string, $encoding);
6805
}
6806

    
6807
/**
6808
 * Count words in a string.
6809
 *
6810
 * Words are defined as things between whitespace.
6811
 *
6812
 * @param string $string The text to be searched for words.
6813
 * @return int The count of words in the specified string
6814
 */
6815
function count_words($string) {
6816
    $string = strip_tags($string);
6817
    return count(preg_split("/\w\b/", $string)) - 1;
6818
}
6819

    
6820
/** Count letters in a string.
6821
 *
6822
 * Letters are defined as chars not in tags and different from whitespace.
6823
 *
6824
 * @param string $string The text to be searched for letters.
6825
 * @return int The count of letters in the specified text.
6826
 */
6827
function count_letters($string) {
6828
/// Loading the textlib singleton instance. We are going to need it.
6829
    $textlib = textlib_get_instance();
6830

    
6831
    $string = strip_tags($string); // Tags are out now
6832
    $string = ereg_replace('[[:space:]]*','',$string); //Whitespace are out now
6833

    
6834
    return $textlib->strlen($string);
6835
}
6836

    
6837
/**
6838
 * Generate and return a random string of the specified length.
6839
 *
6840
 * @param int $length The length of the string to be created.
6841
 * @return string
6842
 */
6843
function random_string ($length=15) {
6844
    $pool  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
6845
    $pool .= 'abcdefghijklmnopqrstuvwxyz';
6846
    $pool .= '0123456789';
6847
    $poollen = strlen($pool);
6848
    mt_srand ((double) microtime() * 1000000);
6849
    $string = '';
6850
    for ($i = 0; $i < $length; $i++) {
6851
        $string .= substr($pool, (mt_rand()%($poollen)), 1);
6852
    }
6853
    return $string;
6854
}
6855

    
6856
/**
6857
 * Generate a complex random string (usefull for md5 salts)
6858
 *
6859
 * This function is based on the above {@link random_string()} however it uses a
6860
 * larger pool of characters and generates a string between 24 and 32 characters
6861
 *
6862
 * @param int $length Optional if set generates a string to exactly this length
6863
 * @return string
6864
 */
6865
function complex_random_string($length=null) {
6866
    $pool  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
6867
    $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
6868
    $poollen = strlen($pool);
6869
    mt_srand ((double) microtime() * 1000000);
6870
    if ($length===null) {
6871
        $length = floor(rand(24,32));
6872
    }
6873
    $string = '';
6874
    for ($i = 0; $i < $length; $i++) {
6875
        $string .= $pool[(mt_rand()%$poollen)];
6876
    }
6877
    return $string;
6878
}
6879

    
6880
/*
6881
 * Given some text (which may contain HTML) and an ideal length,
6882
 * this function truncates the text neatly on a word boundary if possible
6883
 * @param string $text - text to be shortened
6884
 * @param int $ideal - ideal string length
6885
 * @param boolean $exact if false, $text will not be cut mid-word
6886
 * @return string $truncate - shortened string
6887
 */
6888

    
6889
function shorten_text($text, $ideal=30, $exact = false) {
6890

    
6891
    global $CFG;
6892
    $ending = '...';
6893

    
6894
    // if the plain text is shorter than the maximum length, return the whole text
6895
    if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
6896
        return $text;
6897
    }
6898

    
6899
    // Splits on HTML tags. Each open/close/empty tag will be the first thing
6900
    // and only tag in its 'line'
6901
    preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
6902

    
6903
    $total_length = strlen($ending);
6904
    $truncate = '';
6905

    
6906
    // This array stores information about open and close tags and their position
6907
    // in the truncated string. Each item in the array is an object with fields
6908
    // ->open (true if open), ->tag (tag name in lower case), and ->pos
6909
    // (byte position in truncated text)
6910
    $tagdetails = array();
6911

    
6912
    foreach ($lines as $line_matchings) {
6913
        // if there is any html-tag in this line, handle it and add it (uncounted) to the output
6914
        if (!empty($line_matchings[1])) {
6915
            // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
6916
            if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
6917
                    // do nothing
6918
            // if tag is a closing tag (f.e. </b>)
6919
            } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
6920
                // record closing tag
6921
                $tagdetails[] = (object)array('open'=>false,
6922
                    'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6923
            // if tag is an opening tag (f.e. <b>)
6924
            } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
6925
                // record opening tag
6926
                $tagdetails[] = (object)array('open'=>true,
6927
                    'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6928
            }
6929
            // add html-tag to $truncate'd text
6930
            $truncate .= $line_matchings[1];
6931
        }
6932

    
6933
        // calculate the length of the plain text part of the line; handle entities as one character
6934
        $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
6935
        if ($total_length+$content_length > $ideal) {
6936
            // the number of characters which are left
6937
            $left = $ideal - $total_length;
6938
            $entities_length = 0;
6939
            // search for html entities
6940
            if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
6941
                // calculate the real length of all entities in the legal range
6942
                foreach ($entities[0] as $entity) {
6943
                    if ($entity[1]+1-$entities_length <= $left) {
6944
                        $left--;
6945
                        $entities_length += strlen($entity[0]);
6946
                    } else {
6947
                        // no more characters left
6948
                        break;
6949
                    }
6950
                }
6951
            }
6952
            $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
6953
            // maximum lenght is reached, so get off the loop
6954
            break;
6955
        } else {
6956
            $truncate .= $line_matchings[2];
6957
            $total_length += $content_length;
6958
        }
6959

    
6960
        // if the maximum length is reached, get off the loop
6961
        if($total_length >= $ideal) {
6962
            break;
6963
        }
6964
    }
6965

    
6966
    // if the words shouldn't be cut in the middle...
6967
    if (!$exact) {
6968
        // ...search the last occurance of a space...
6969
        for ($k=strlen($truncate);$k>0;$k--) {
6970
            if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
6971
                if ($char == '.' or $char == ' ') {
6972
                    $breakpos = $k+1;
6973
                    break;
6974
                } else if (ord($char) >= 0xE0) {  // Chinese/Japanese/Korean text
6975
                    $breakpos = $k;               // can be truncated at any UTF-8
6976
                    break;                        // character boundary.
6977
                }
6978
            }
6979
        }
6980

    
6981
        if (isset($breakpos)) {
6982
            // ...and cut the text in this position
6983
            $truncate = substr($truncate, 0, $breakpos);
6984
        }
6985
    }
6986

    
6987
    // add the defined ending to the text
6988
    $truncate .= $ending;
6989

    
6990
    // Now calculate the list of open html tags based on the truncate position
6991
    $open_tags = array();
6992
    foreach ($tagdetails as $taginfo) {
6993
        if(isset($breakpos) && $taginfo->pos >= $breakpos) {
6994
            // Don't include tags after we made the break!
6995
            break;
6996
        }
6997
        if($taginfo->open) {
6998
            // add tag to the beginning of $open_tags list
6999
            array_unshift($open_tags, $taginfo->tag);
7000
        } else {
7001
            $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
7002
            if ($pos !== false) {
7003
                unset($open_tags[$pos]);
7004
            }
7005
        }
7006
    }
7007

    
7008
    // close all unclosed html-tags
7009
    foreach ($open_tags as $tag) {
7010
        $truncate .= '</' . $tag . '>';
7011
    }
7012

    
7013
    return $truncate;
7014
}
7015

    
7016

    
7017
/**
7018
 * Given dates in seconds, how many weeks is the date from startdate
7019
 * The first week is 1, the second 2 etc ...
7020
 *
7021
 * @uses WEEKSECS
7022
 * @param ? $startdate ?
7023
 * @param ? $thedate ?
7024
 * @return string
7025
 * @todo Finish documenting this function
7026
 */
7027
function getweek ($startdate, $thedate) {
7028
    if ($thedate < $startdate) {   // error
7029
        return 0;
7030
    }
7031

    
7032
    return floor(($thedate - $startdate) / WEEKSECS) + 1;
7033
}
7034

    
7035
/**
7036
 * returns a randomly generated password of length $maxlen.  inspired by
7037
 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
7038
 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
7039
 *
7040
 * @param int $maxlen  The maximum size of the password being generated.
7041
 * @return string
7042
 */
7043
function generate_password($maxlen=10) {
7044
    global $CFG;
7045

    
7046
    if (empty($CFG->passwordpolicy)) {
7047
        $fillers = PASSWORD_DIGITS;
7048
        $wordlist = file($CFG->wordlist);
7049
        $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7050
        $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7051
        $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
7052
        $password = $word1 . $filler1 . $word2;
7053
    } else {
7054
        $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
7055
        $digits = $CFG->minpassworddigits;
7056
        $lower = $CFG->minpasswordlower;
7057
        $upper = $CFG->minpasswordupper;
7058
        $nonalphanum = $CFG->minpasswordnonalphanum;
7059
        $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
7060

    
7061
        // Make sure we have enough characters to fulfill
7062
        // complexity requirements
7063
        $passworddigits = PASSWORD_DIGITS;
7064
        while ($digits > strlen($passworddigits)) {
7065
            $passworddigits .= PASSWORD_DIGITS;
7066
        }
7067
        $passwordlower = PASSWORD_LOWER;
7068
        while ($lower > strlen($passwordlower)) {
7069
            $passwordlower .= PASSWORD_LOWER;
7070
        }
7071
        $passwordupper = PASSWORD_UPPER;
7072
        while ($upper > strlen($passwordupper)) {
7073
            $passwordupper .= PASSWORD_UPPER;
7074
        }
7075
        $passwordnonalphanum = PASSWORD_NONALPHANUM;
7076
        while ($nonalphanum > strlen($passwordnonalphanum)) {
7077
            $passwordnonalphanum .= PASSWORD_NONALPHANUM;
7078
        }
7079

    
7080
        // Now mix and shuffle it all
7081
        $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
7082
                                 substr(str_shuffle ($passwordupper), 0, $upper) .
7083
                                 substr(str_shuffle ($passworddigits), 0, $digits) .
7084
                                 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
7085
                                 substr(str_shuffle ($passwordlower .
7086
                                                     $passwordupper .
7087
                                                     $passworddigits .
7088
                                                     $passwordnonalphanum), 0 , $additional));
7089
    }
7090

    
7091
    return substr ($password, 0, $maxlen);
7092
}
7093

    
7094
/**
7095
 * Given a float, prints it nicely.
7096
 * Localized floats must not be used in calculations!
7097
 *
7098
 * @param float $flaot The float to print
7099
 * @param int $places The number of decimal places to print.
7100
 * @param bool $localized use localized decimal separator
7101
 * @return string locale float
7102
 */
7103
function format_float($float, $decimalpoints=1, $localized=true) {
7104
    if (is_null($float)) {
7105
        return '';
7106
    }
7107
    if ($localized) {
7108
        return number_format($float, $decimalpoints, get_string('decsep'), '');
7109
    } else {
7110
        return number_format($float, $decimalpoints, '.', '');
7111
    }
7112
}
7113

    
7114
/**
7115
 * Converts locale specific floating point/comma number back to standard PHP float value
7116
 * Do NOT try to do any math operations before this conversion on any user submitted floats!
7117
 *
7118
 * @param  string $locale_float locale aware float representation
7119
 */
7120
function unformat_float($locale_float) {
7121
    $locale_float = trim($locale_float);
7122

    
7123
    if ($locale_float == '') {
7124
        return null;
7125
    }
7126

    
7127
    $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
7128

    
7129
    return (float)str_replace(get_string('decsep'), '.', $locale_float);
7130
}
7131

    
7132
/**
7133
 * Given a simple array, this shuffles it up just like shuffle()
7134
 * Unlike PHP's shuffle() this function works on any machine.
7135
 *
7136
 * @param array $array The array to be rearranged
7137
 * @return array
7138
 */
7139
function swapshuffle($array) {
7140

    
7141
    srand ((double) microtime() * 10000000);
7142
    $last = count($array) - 1;
7143
    for ($i=0;$i<=$last;$i++) {
7144
        $from = rand(0,$last);
7145
        $curr = $array[$i];
7146
        $array[$i] = $array[$from];
7147
        $array[$from] = $curr;
7148
    }
7149
    return $array;
7150
}
7151

    
7152
/**
7153
 * Like {@link swapshuffle()}, but works on associative arrays
7154
 *
7155
 * @param array $array The associative array to be rearranged
7156
 * @return array
7157
 */
7158
function swapshuffle_assoc($array) {
7159

    
7160
    $newarray = array();
7161
    $newkeys = swapshuffle(array_keys($array));
7162

    
7163
    foreach ($newkeys as $newkey) {
7164
        $newarray[$newkey] = $array[$newkey];
7165
    }
7166
    return $newarray;
7167
}
7168

    
7169
/**
7170
 * Given an arbitrary array, and a number of draws,
7171
 * this function returns an array with that amount
7172
 * of items.  The indexes are retained.
7173
 *
7174
 * @param array $array ?
7175
 * @param ? $draws ?
7176
 * @return ?
7177
 * @todo Finish documenting this function
7178
 */
7179
function draw_rand_array($array, $draws) {
7180
    srand ((double) microtime() * 10000000);
7181

    
7182
    $return = array();
7183

    
7184
    $last = count($array);
7185

    
7186
    if ($draws > $last) {
7187
        $draws = $last;
7188
    }
7189

    
7190
    while ($draws > 0) {
7191
        $last--;
7192

    
7193
        $keys = array_keys($array);
7194
        $rand = rand(0, $last);
7195

    
7196
        $return[$keys[$rand]] = $array[$keys[$rand]];
7197
        unset($array[$keys[$rand]]);
7198

    
7199
        $draws--;
7200
    }
7201

    
7202
    return $return;
7203
}
7204

    
7205
/**
7206
 * microtime_diff
7207
 *
7208
 * @param string $a ?
7209
 * @param string $b ?
7210
 * @return string
7211
 * @todo Finish documenting this function
7212
 */
7213
function microtime_diff($a, $b) {
7214
    list($a_dec, $a_sec) = explode(' ', $a);
7215
    list($b_dec, $b_sec) = explode(' ', $b);
7216
    return $b_sec - $a_sec + $b_dec - $a_dec;
7217
}
7218

    
7219
/**
7220
 * Given a list (eg a,b,c,d,e) this function returns
7221
 * an array of 1->a, 2->b, 3->c etc
7222
 *
7223
 * @param array $list ?
7224
 * @param string $separator ?
7225
 * @todo Finish documenting this function
7226
 */
7227
function make_menu_from_list($list, $separator=',') {
7228

    
7229
    $array = array_reverse(explode($separator, $list), true);
7230
    foreach ($array as $key => $item) {
7231
        $outarray[$key+1] = trim($item);
7232
    }
7233
    return $outarray;
7234
}
7235

    
7236
/**
7237
 * Creates an array that represents all the current grades that
7238
 * can be chosen using the given grading type.  Negative numbers
7239
 * are scales, zero is no grade, and positive numbers are maximum
7240
 * grades.
7241
 *
7242
 * @param int $gradingtype ?
7243
 * return int
7244
 * @todo Finish documenting this function
7245
 */
7246
function make_grades_menu($gradingtype) {
7247
    $grades = array();
7248
    if ($gradingtype < 0) {
7249
        if ($scale = get_record('scale', 'id', - $gradingtype)) {
7250
            return make_menu_from_list($scale->scale);
7251
        }
7252
    } else if ($gradingtype > 0) {
7253
        for ($i=$gradingtype; $i>=0; $i--) {
7254
            $grades[$i] = $i .' / '. $gradingtype;
7255
        }
7256
        return $grades;
7257
    }
7258
    return $grades;
7259
}
7260

    
7261
/**
7262
 * This function returns the nummber of activities
7263
 * using scaleid in a courseid
7264
 *
7265
 * @param int $courseid ?
7266
 * @param int $scaleid ?
7267
 * @return int
7268
 * @todo Finish documenting this function
7269
 */
7270
function course_scale_used($courseid, $scaleid) {
7271

    
7272
    global $CFG;
7273

    
7274
    $return = 0;
7275

    
7276
    if (!empty($scaleid)) {
7277
        if ($cms = get_course_mods($courseid)) {
7278
            foreach ($cms as $cm) {
7279
                //Check cm->name/lib.php exists
7280
                if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
7281
                    include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
7282
                    $function_name = $cm->modname.'_scale_used';
7283
                    if (function_exists($function_name)) {
7284
                        if ($function_name($cm->instance,$scaleid)) {
7285
                            $return++;
7286
                        }
7287
                    }
7288
                }
7289
            }
7290
        }
7291

    
7292
        // check if any course grade item makes use of the scale
7293
        $return += count_records('grade_items', 'courseid', $courseid, 'scaleid', $scaleid);
7294

    
7295
        // check if any outcome in the course makes use of the scale
7296
        $return += count_records_sql("SELECT COUNT(*)
7297
                                      FROM {$CFG->prefix}grade_outcomes_courses goc,
7298
                                           {$CFG->prefix}grade_outcomes go
7299
                                      WHERE go.id = goc.outcomeid
7300
                                        AND go.scaleid = $scaleid
7301
                                        AND goc.courseid = $courseid");
7302
    }
7303
    return $return;
7304
}
7305

    
7306
/**
7307
 * This function returns the nummber of activities
7308
 * using scaleid in the entire site
7309
 *
7310
 * @param int $scaleid ?
7311
 * @return int
7312
 * @todo Finish documenting this function. Is return type correct?
7313
 */
7314
function site_scale_used($scaleid,&$courses) {
7315

    
7316
    global $CFG;
7317

    
7318
    $return = 0;
7319

    
7320
    if (!is_array($courses) || count($courses) == 0) {
7321
        $courses = get_courses("all",false,"c.id,c.shortname");
7322
    }
7323

    
7324
    if (!empty($scaleid)) {
7325
        if (is_array($courses) && count($courses) > 0) {
7326
            foreach ($courses as $course) {
7327
                $return += course_scale_used($course->id,$scaleid);
7328
            }
7329
        }
7330
    }
7331
    return $return;
7332
}
7333

    
7334
/**
7335
 * make_unique_id_code
7336
 *
7337
 * @param string $extra ?
7338
 * @return string
7339
 * @todo Finish documenting this function
7340
 */
7341
function make_unique_id_code($extra='') {
7342

    
7343
    $hostname = 'unknownhost';
7344
    if (!empty($_SERVER['HTTP_HOST'])) {
7345
        $hostname = $_SERVER['HTTP_HOST'];
7346
    } else if (!empty($_ENV['HTTP_HOST'])) {
7347
        $hostname = $_ENV['HTTP_HOST'];
7348
    } else if (!empty($_SERVER['SERVER_NAME'])) {
7349
        $hostname = $_SERVER['SERVER_NAME'];
7350
    } else if (!empty($_ENV['SERVER_NAME'])) {
7351
        $hostname = $_ENV['SERVER_NAME'];
7352
    }
7353

    
7354
    $date = gmdate("ymdHis");
7355

    
7356
    $random =  random_string(6);
7357

    
7358
    if ($extra) {
7359
        return $hostname .'+'. $date .'+'. $random .'+'. $extra;
7360
    } else {
7361
        return $hostname .'+'. $date .'+'. $random;
7362
    }
7363
}
7364

    
7365

    
7366
/**
7367
 * Function to check the passed address is within the passed subnet
7368
 *
7369
 * The parameter is a comma separated string of subnet definitions.
7370
 * Subnet strings can be in one of three formats:
7371
 *   1: xxx.xxx.xxx.xxx/xx
7372
 *   2: xxx.xxx
7373
 *   3: xxx.xxx.xxx.xxx-xxx   //a range of IP addresses in the last group.
7374
 * Code for type 1 modified from user posted comments by mediator at
7375
 * {@link http://au.php.net/manual/en/function.ip2long.php}
7376
 *
7377
 * TODO one day we will have to make this work with IP6.
7378
 *
7379
 * @param string $addr    The address you are checking
7380
 * @param string $subnetstr    The string of subnet addresses
7381
 * @return bool
7382
 */
7383
function address_in_subnet($addr, $subnetstr) {
7384

    
7385
    $subnets = explode(',', $subnetstr);
7386
    $found = false;
7387
    $addr = trim($addr);
7388

    
7389
    foreach ($subnets as $subnet) {
7390
        $subnet = trim($subnet);
7391
        if (strpos($subnet, '/') !== false) { /// type 1
7392
            list($ip, $mask) = explode('/', $subnet);
7393
            if (!is_number($mask) || $mask < 0 || $mask > 32) {
7394
                continue;
7395
            }
7396
            if ($mask == 0) {
7397
                return true;
7398
            }
7399
            if ($mask == 32) {
7400
                if ($ip === $addr) {
7401
                    return true;
7402
                }
7403
                continue;
7404
            }
7405
            $mask = 0xffffffff << (32 - $mask);
7406
            $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
7407
        } else if (strpos($subnet, '-') !== false)  {/// type 3
7408
            $subnetparts = explode('.', $subnet);
7409
            $addrparts = explode('.', $addr);
7410
            $subnetrange = explode('-', array_pop($subnetparts));
7411
            if (count($subnetrange) == 2) {
7412
                $lastaddrpart = array_pop($addrparts);
7413
                $found = ($subnetparts == $addrparts &&
7414
                        $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
7415
            }
7416
        } else { /// type 2
7417
            if ($subnet[strlen($subnet) - 1] != '.') {
7418
                $subnet .= '.';
7419
            }
7420
            $found = (strpos($addr . '.', $subnet) === 0);
7421
        }
7422

    
7423
        if ($found) {
7424
            break;
7425
        }
7426
    }
7427
    return $found;
7428
}
7429

    
7430
/**
7431
 * This function sets the $HTTPSPAGEREQUIRED global
7432
 * (used in some parts of moodle to change some links)
7433
 * and calculate the proper wwwroot to be used
7434
 *
7435
 * By using this function properly, we can ensure 100% https-ized pages
7436
 * at our entire discretion (login, forgot_password, change_password)
7437
 */
7438
function httpsrequired() {
7439

    
7440
    global $CFG, $HTTPSPAGEREQUIRED;
7441

    
7442
    if (!empty($CFG->loginhttps)) {
7443
        $HTTPSPAGEREQUIRED = true;
7444
        $CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
7445
        $CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
7446

    
7447
        // change theme URLs to https
7448
        theme_setup();
7449

    
7450
    } else {
7451
        $CFG->httpswwwroot = $CFG->wwwroot;
7452
        $CFG->httpsthemewww = $CFG->themewww;
7453
    }
7454
}
7455

    
7456
/**
7457
 * For outputting debugging info
7458
 *
7459
 * @uses STDOUT
7460
 * @param string $string ?
7461
 * @param string $eol ?
7462
 * @todo Finish documenting this function
7463
 */
7464
function mtrace($string, $eol="\n", $sleep=0) {
7465

    
7466
    if (defined('STDOUT')) {
7467
        fwrite(STDOUT, $string.$eol);
7468
    } else {
7469
        echo $string . $eol;
7470
    }
7471

    
7472
    flush();
7473

    
7474
    //delay to keep message on user's screen in case of subsequent redirect
7475
    if ($sleep) {
7476
        sleep($sleep);
7477
    }
7478
}
7479

    
7480
//Replace 1 or more slashes or backslashes to 1 slash
7481
function cleardoubleslashes ($path) {
7482
    return preg_replace('/(\/|\\\){1,}/','/',$path);
7483
}
7484

    
7485
function zip_files ($originalfiles, $destination) {
7486
//Zip an array of files/dirs to a destination zip file
7487
//Both parameters must be FULL paths to the files/dirs
7488

    
7489
    global $CFG;
7490

    
7491
    //Extract everything from destination
7492
    $path_parts = pathinfo(cleardoubleslashes($destination));
7493
    $destpath = $path_parts["dirname"];       //The path of the zip file
7494
    $destfilename = $path_parts["basename"];  //The name of the zip file
7495
    $extension = $path_parts["extension"];    //The extension of the file
7496

    
7497
    //If no file, error
7498
    if (empty($destfilename)) {
7499
        return false;
7500
    }
7501

    
7502
    //If no extension, add it
7503
    if (empty($extension)) {
7504
        $extension = 'zip';
7505
        $destfilename = $destfilename.'.'.$extension;
7506
    }
7507

    
7508
    //Check destination path exists
7509
    if (!is_dir($destpath)) {
7510
        return false;
7511
    }
7512

    
7513
    //Check destination path is writable. TODO!!
7514

    
7515
    //Clean destination filename
7516
    $destfilename = clean_filename($destfilename);
7517

    
7518
    //Now check and prepare every file
7519
    $files = array();
7520
    $origpath = NULL;
7521

    
7522
    foreach ($originalfiles as $file) {  //Iterate over each file
7523
        //Check for every file
7524
        $tempfile = cleardoubleslashes($file); // no doubleslashes!
7525
        //Calculate the base path for all files if it isn't set
7526
        if ($origpath === NULL) {
7527
            $origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
7528
        }
7529
        //See if the file is readable
7530
        if (!is_readable($tempfile)) {  //Is readable
7531
            continue;
7532
        }
7533
        //See if the file/dir is in the same directory than the rest
7534
        if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
7535
            continue;
7536
        }
7537
        //Add the file to the array
7538
        $files[] = $tempfile;
7539
    }
7540

    
7541
    //Everything is ready:
7542
    //    -$origpath is the path where ALL the files to be compressed reside (dir).
7543
    //    -$destpath is the destination path where the zip file will go (dir).
7544
    //    -$files is an array of files/dirs to compress (fullpath)
7545
    //    -$destfilename is the name of the zip file (without path)
7546

    
7547
    //print_object($files);                  //Debug
7548

    
7549
    if (empty($CFG->zip)) {    // Use built-in php-based zip function
7550

    
7551
        include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7552
        //rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
7553
        $zipfiles = array();
7554
        $start = strlen($origpath)+1;
7555
        foreach($files as $file) {
7556
            $tf = array();
7557
            $tf[PCLZIP_ATT_FILE_NAME] = $file;
7558
            $tf[PCLZIP_ATT_FILE_NEW_FULL_NAME] = substr($file, $start);
7559
            $zipfiles[] = $tf;
7560
        }
7561
        //create the archive
7562
        $archive = new PclZip(cleardoubleslashes("$destpath/$destfilename"));
7563
        if (($list = $archive->create($zipfiles) == 0)) {
7564
            notice($archive->errorInfo(true));
7565
            return false;
7566
        }
7567

    
7568
    } else {                   // Use external zip program
7569

    
7570
        $filestozip = "";
7571
        foreach ($files as $filetozip) {
7572
            $filestozip .= escapeshellarg(basename($filetozip));
7573
            $filestozip .= " ";
7574
        }
7575
        //Construct the command
7576
        $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7577
        $command = 'cd '.escapeshellarg($origpath).$separator.
7578
                    escapeshellarg($CFG->zip).' -r '.
7579
                    escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
7580
        //All converted to backslashes in WIN
7581
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7582
            $command = str_replace('/','\\',$command);
7583
        }
7584
        Exec($command);
7585
    }
7586
    return true;
7587
}
7588

    
7589
function unzip_file ($zipfile, $destination = '', $showstatus = true) {
7590
//Unzip one zip file to a destination dir
7591
//Both parameters must be FULL paths
7592
//If destination isn't specified, it will be the
7593
//SAME directory where the zip file resides.
7594

    
7595
    global $CFG;
7596

    
7597
    //Extract everything from zipfile
7598
    $path_parts = pathinfo(cleardoubleslashes($zipfile));
7599
    $zippath = $path_parts["dirname"];       //The path of the zip file
7600
    $zipfilename = $path_parts["basename"];  //The name of the zip file
7601
    $extension = $path_parts["extension"];    //The extension of the file
7602

    
7603
    //If no file, error
7604
    if (empty($zipfilename)) {
7605
        return false;
7606
    }
7607

    
7608
    //If no extension, error
7609
    if (empty($extension)) {
7610
        return false;
7611
    }
7612

    
7613
    //Clear $zipfile
7614
    $zipfile = cleardoubleslashes($zipfile);
7615

    
7616
    //Check zipfile exists
7617
    if (!file_exists($zipfile)) {
7618
        return false;
7619
    }
7620

    
7621
    //If no destination, passed let's go with the same directory
7622
    if (empty($destination)) {
7623
        $destination = $zippath;
7624
    }
7625

    
7626
    //Clear $destination
7627
    $destpath = rtrim(cleardoubleslashes($destination), "/");
7628

    
7629
    //Check destination path exists
7630
    if (!is_dir($destpath)) {
7631
        return false;
7632
    }
7633

    
7634
    //Check destination path is writable. TODO!!
7635

    
7636
    //Everything is ready:
7637
    //    -$zippath is the path where the zip file resides (dir)
7638
    //    -$zipfilename is the name of the zip file (without path)
7639
    //    -$destpath is the destination path where the zip file will uncompressed (dir)
7640

    
7641
    $list = array();
7642

    
7643
    require_once("$CFG->libdir/filelib.php");
7644

    
7645
    do {
7646
        $temppath = "$CFG->dataroot/temp/unzip/".random_string(10);
7647
    } while (file_exists($temppath));
7648
    if (!check_dir_exists($temppath, true, true)) {
7649
        return false;
7650
    }
7651

    
7652
    if (empty($CFG->unzip)) {    // Use built-in php-based unzip function
7653

    
7654
        include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7655
        $archive = new PclZip(cleardoubleslashes("$zippath/$zipfilename"));
7656
        if (!$list = $archive->extract(PCLZIP_OPT_PATH, $temppath,
7657
                                       PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename',
7658
                                       PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $temppath)) {
7659
            if (!empty($showstatus)) {
7660
                notice($archive->errorInfo(true));
7661
            }
7662
            return false;
7663
        }
7664

    
7665
    } else {                     // Use external unzip program
7666

    
7667
        $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7668
        $redirection = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? '' : ' 2>&1';
7669

    
7670
        $command = 'cd '.escapeshellarg($zippath).$separator.
7671
                    escapeshellarg($CFG->unzip).' -o '.
7672
                    escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
7673
                    escapeshellarg($temppath).$redirection;
7674
        //All converted to backslashes in WIN
7675
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7676
            $command = str_replace('/','\\',$command);
7677
        }
7678
        Exec($command,$list);
7679
    }
7680

    
7681
    unzip_process_temp_dir($temppath, $destpath);
7682
    fulldelete($temppath);
7683

    
7684
    //Display some info about the unzip execution
7685
    if ($showstatus) {
7686
        unzip_show_status($list, $temppath, $destpath);
7687
    }
7688

    
7689
    return true;
7690
}
7691

    
7692
/**
7693
 * Sanitize temporary unzipped files and move to target dir.
7694
 * @param string $temppath path to temporary dir with unzip output
7695
 * @param string $destpath destination path
7696
 * @return void
7697
 */
7698
function unzip_process_temp_dir($temppath, $destpath) {
7699
    global $CFG;
7700

    
7701
    $filepermissions = ($CFG->directorypermissions & 0666); // strip execute flags
7702

    
7703
    if (check_dir_exists($destpath, true, true)) {
7704
        $currdir = opendir($temppath);
7705
        while (false !== ($file = readdir($currdir))) {
7706
            if ($file <> ".." && $file <> ".") {
7707
                $fullfile = "$temppath/$file";
7708
                if (is_link($fullfile)) {
7709
                    //somebody tries to sneak in symbolik link - no way!
7710
                    continue;
7711
                }
7712
                $cleanfile = clean_param($file, PARAM_FILE); // no dangerous chars
7713
                if ($cleanfile === '') {
7714
                    // invalid file name
7715
                    continue;
7716
                }
7717
                if ($cleanfile !== $file and file_exists("$temppath/$cleanfile")) {
7718
                    // eh, weird chars collision detected
7719
                    continue;
7720
                }
7721
                $descfile = "$destpath/$cleanfile";
7722
                if (is_dir($fullfile)) {
7723
                    // recurse into subdirs
7724
                    unzip_process_temp_dir($fullfile, $descfile);
7725
                }
7726
                if (is_file($fullfile)) {
7727
                    // rename and move the file
7728
                    if (file_exists($descfile)) {
7729
                        //override existing files
7730
                        unlink($descfile);
7731
                    }
7732
                    rename($fullfile, $descfile);
7733
                    chmod($descfile, $filepermissions);
7734
                }
7735
            }
7736
        }
7737
        closedir($currdir);
7738
    }
7739
}
7740

    
7741
function unzip_cleanfilename ($p_event, &$p_header) {
7742
//This function is used as callback in unzip_file() function
7743
//to clean illegal characters for given platform and to prevent directory traversal.
7744
//Produces the same result as info-zip unzip.
7745
    $p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
7746
    $p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
7747
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7748
        $p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
7749
        $p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
7750
    } else {
7751
        //Add filtering for other systems here
7752
        // BSD: none (tested)
7753
        // Linux: ??
7754
        // MacosX: ??
7755
    }
7756
    $p_header['filename'] = cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
7757
    return 1;
7758
}
7759

    
7760
function unzip_show_status($list, $removepath, $removepath2) {
7761
//This function shows the results of the unzip execution
7762
//depending of the value of the $CFG->zip, results will be
7763
//text or an array of files.
7764

    
7765
    global $CFG;
7766

    
7767
    if (empty($CFG->unzip)) {    // Use built-in php-based zip function
7768
        $strname = get_string("name");
7769
        $strsize = get_string("size");
7770
        $strmodified = get_string("modified");
7771
        $strstatus = get_string("status");
7772
        echo "<table width=\"640\">";
7773
        echo "<tr><th class=\"header\" scope=\"col\">$strname</th>";
7774
        echo "<th class=\"header\" align=\"right\" scope=\"col\">$strsize</th>";
7775
        echo "<th class=\"header\" align=\"right\" scope=\"col\">$strmodified</th>";
7776
        echo "<th class=\"header\" align=\"right\" scope=\"col\">$strstatus</th></tr>";
7777
        foreach ($list as $item) {
7778
            echo "<tr>";
7779
            $item['filename'] = str_replace(cleardoubleslashes($removepath).'/', "", $item['filename']);
7780
            $item['filename'] = str_replace(cleardoubleslashes($removepath2).'/', "", $item['filename']);
7781
            echo '<td align="left" style="white-space:nowrap ">'.s(clean_param($item['filename'], PARAM_PATH)).'</td>';
7782
            if (! $item['folder']) {
7783
                echo '<td align="right" style="white-space:nowrap ">'.display_size($item['size']).'</td>';
7784
            } else {
7785
                echo "<td>&nbsp;</td>";
7786
            }
7787
            $filedate  = userdate($item['mtime'], get_string("strftimedatetime"));
7788
            echo '<td align="right" style="white-space:nowrap ">'.$filedate.'</td>';
7789
            echo '<td align="right" style="white-space:nowrap ">'.$item['status'].'</td>';
7790
            echo "</tr>";
7791
        }
7792
        echo "</table>";
7793

    
7794
    } else {                   // Use external zip program
7795
        print_simple_box_start("center");
7796
        echo "<pre>";
7797
        foreach ($list as $item) {
7798
            $item = str_replace(cleardoubleslashes($removepath.'/'), '', $item);
7799
            $item = str_replace(cleardoubleslashes($removepath2.'/'), '', $item);
7800
            echo s($item).'<br />';
7801
        }
7802
        echo "</pre>";
7803
        print_simple_box_end();
7804
    }
7805
}
7806

    
7807
/**
7808
 * Returns most reliable client address
7809
 *
7810
 * @return string The remote IP address
7811
 */
7812
define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
7813
define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
7814
function getremoteaddr() {
7815
    global $CFG;
7816

    
7817
    if (empty($CFG->getremoteaddrconf)) {
7818
        // This will happen, for example, before just after the upgrade, as the
7819
        // user is redirected to the admin screen.
7820
        $variablestoskip = 0;
7821
    } else {
7822
        $variablestoskip = $CFG->getremoteaddrconf;
7823
    }
7824
    if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
7825
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
7826
            return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
7827
        }
7828
    }
7829
    if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
7830
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
7831
            return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
7832
        }
7833
    }
7834
    if (!empty($_SERVER['REMOTE_ADDR'])) {
7835
        return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
7836
    } else {
7837
        return null;
7838
    }
7839
}
7840

    
7841
/**
7842
 * Cleans a remote address ready to put into the log table
7843
 */
7844
function cleanremoteaddr($addr) {
7845
    $originaladdr = $addr;
7846
    $matches = array();
7847
    // first get all things that look like IP addresses.
7848
    if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER)) {
7849
        return '';
7850
    }
7851
    $goodmatches = array();
7852
    $lanmatches = array();
7853
    foreach ($matches as $match) {
7854
        //        print_r($match);
7855
        // check to make sure it's not an internal address.
7856
        // the following are reserved for private lans...
7857
        // 10.0.0.0 - 10.255.255.255
7858
        // 172.16.0.0 - 172.31.255.255
7859
        // 192.168.0.0 - 192.168.255.255
7860
        // 169.254.0.0 -169.254.255.255
7861
        $bits = explode('.',$match[0]);
7862
        if (count($bits) != 4) {
7863
            // weird, preg match shouldn't give us it.
7864
            continue;
7865
        }
7866
        if (($bits[0] == 10)
7867
            || ($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
7868
            || ($bits[0] == 192 && $bits[1] == 168)
7869
            || ($bits[0] == 169 && $bits[1] == 254)) {
7870
            $lanmatches[] = $match[0];
7871
            continue;
7872
        }
7873
        // finally, it's ok
7874
        $goodmatches[] = $match[0];
7875
    }
7876
    if (!count($goodmatches)) {
7877
        // perhaps we have a lan match, it's probably better to return that.
7878
        if (!count($lanmatches)) {
7879
            return '';
7880
        } else {
7881
            return array_pop($lanmatches);
7882
        }
7883
    }
7884
    if (count($goodmatches) == 1) {
7885
        return $goodmatches[0];
7886
    }
7887
    //Commented out following because there are so many, and it clogs the logs   MDL-13544
7888
    //error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
7889

    
7890
    // We need to return something, so return the first
7891
    return array_pop($goodmatches);
7892
}
7893

    
7894
/**
7895
 * file_put_contents is only supported by php 5.0 and higher
7896
 * so if it is not predefined, define it here
7897
 *
7898
 * @param $file full path of the file to write
7899
 * @param $contents contents to be sent
7900
 * @return number of bytes written (false on error)
7901
 */
7902
if(!function_exists('file_put_contents')) {
7903
    function file_put_contents($file, $contents) {
7904
        $result = false;
7905
        if ($f = fopen($file, 'w')) {
7906
            $result = fwrite($f, $contents);
7907
            fclose($f);
7908
        }
7909
        return $result;
7910
    }
7911
}
7912

    
7913
/**
7914
 * The clone keyword is only supported from PHP 5 onwards.
7915
 * The behaviour of $obj2 = $obj1 differs fundamentally
7916
 * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
7917
 * created, in PHP 5 $obj1 is referenced. To create a copy
7918
 * in PHP 5 the clone keyword was introduced. This function
7919
 * simulates this behaviour for PHP < 5.0.0.
7920
 * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
7921
 *
7922
 * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
7923
 * Found a better implementation (more checks and possibilities) from PEAR:
7924
 * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
7925
 *
7926
 * @param object $obj
7927
 * @return object
7928
 */
7929
if(!check_php_version('5.0.0')) {
7930
// the eval is needed to prevent PHP 5 from getting a parse error!
7931
eval('
7932
    function clone($obj) {
7933
    /// Sanity check
7934
        if (!is_object($obj)) {
7935
            user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
7936
            return;
7937
        }
7938

7939
    /// Use serialize/unserialize trick to deep copy the object
7940
        $obj = unserialize(serialize($obj));
7941

7942
    /// If there is a __clone method call it on the "new" class
7943
        if (method_exists($obj, \'__clone\')) {
7944
            $obj->__clone();
7945
        }
7946

7947
        return $obj;
7948
    }
7949

7950
    // Supply the PHP5 function scandir() to older versions.
7951
    function scandir($directory) {
7952
        $files = array();
7953
        if ($dh = opendir($directory)) {
7954
            while (($file = readdir($dh)) !== false) {
7955
               $files[] = $file;
7956
            }
7957
            closedir($dh);
7958
        }
7959
        return $files;
7960
    }
7961

7962
    // Supply the PHP5 function array_combine() to older versions.
7963
    function array_combine($keys, $values) {
7964
        if (!is_array($keys) || !is_array($values) || count($keys) != count($values)) {
7965
            return false;
7966
        }
7967
        reset($values);
7968
        $result = array();
7969
        foreach ($keys as $key) {
7970
            $result[$key] = current($values);
7971
            next($values);
7972
        }
7973
        return $result;
7974
    }
7975
');
7976
}
7977

    
7978
/**
7979
 * This function will make a complete copy of anything it's given,
7980
 * regardless of whether it's an object or not.
7981
 * @param mixed $thing
7982
 * @return mixed
7983
 */
7984
function fullclone($thing) {
7985
    return unserialize(serialize($thing));
7986
}
7987

    
7988

    
7989
/*
7990
 * This function expects to called during shutdown
7991
 * should be set via register_shutdown_function()
7992
 * in lib/setup.php .
7993
 *
7994
 * Right now we do it only if we are under apache, to
7995
 * make sure apache children that hog too much mem are
7996
 * killed.
7997
 *
7998
 */
7999
function moodle_request_shutdown() {
8000

    
8001
    global $CFG;
8002

    
8003
    // initially, we are only ever called under apache
8004
    // but check just in case
8005
    if (function_exists('apache_child_terminate')
8006
        && function_exists('memory_get_usage')
8007
        && ini_get_bool('child_terminate')) {
8008
        if (empty($CFG->apachemaxmem)) {
8009
            $CFG->apachemaxmem = 25000000; // default 25MiB
8010
        }
8011
        if (memory_get_usage() > (int)$CFG->apachemaxmem) {
8012
            trigger_error('Mem usage over $CFG->apachemaxmem: marking child for reaping.');
8013
            @apache_child_terminate();
8014
        }
8015
    }
8016
    if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
8017
        if (defined('MDL_PERFTOLOG')) {
8018
            $perf = get_performance_info();
8019
            error_log("PERF: " . $perf['txt']);
8020
        }
8021
        if (defined('MDL_PERFINC')) {
8022
            $inc = get_included_files();
8023
            $ts  = 0;
8024
            foreach($inc as $f) {
8025
                if (preg_match(':^/:', $f)) {
8026
                    $fs  =  filesize($f);
8027
                    $ts  += $fs;
8028
                    $hfs =  display_size($fs);
8029
                    error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
8030
                              , NULL, NULL, 0);
8031
                } else {
8032
                    error_log($f , NULL, NULL, 0);
8033
                }
8034
            }
8035
            if ($ts > 0 ) {
8036
                $hts = display_size($ts);
8037
                error_log("Total size of files included: $ts ($hts)");
8038
            }
8039
        }
8040
    }
8041
}
8042

    
8043
/**
8044
 * If new messages are waiting for the current user, then return
8045
 * Javascript code to create a popup window
8046
 *
8047
 * @return string Javascript code
8048
 */
8049
function message_popup_window() {
8050
    global $USER;
8051

    
8052
    $popuplimit = 30;     // Minimum seconds between popups
8053

    
8054
    if (!defined('MESSAGE_WINDOW')) {
8055
        if (!empty($USER->id) and !isguestuser()) {
8056
            if (!isset($USER->message_lastpopup)) {
8057
                $USER->message_lastpopup = 0;
8058
            }
8059
            if ((time() - $USER->message_lastpopup) > $popuplimit) {  /// It's been long enough
8060
                if (get_user_preferences('message_showmessagewindow', 1) == 1) {
8061
                    if (count_records_select('message', 'useridto = \''.$USER->id.'\' AND timecreated > \''.$USER->message_lastpopup.'\'')) {
8062
                        $USER->message_lastpopup = time();
8063
                        return '<script type="text/javascript">'."\n//<![CDATA[\n openpopup('/message/index.php', 'message',
8064
                        'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0);\n//]]>\n</script>";
8065
                    }
8066
                }
8067
            }
8068
        }
8069
    }
8070

    
8071
    return '';
8072
}
8073

    
8074
// Used to make sure that $min <= $value <= $max
8075
function bounded_number($min, $value, $max) {
8076
    if($value < $min) {
8077
        return $min;
8078
    }
8079
    if($value > $max) {
8080
        return $max;
8081
    }
8082
    return $value;
8083
}
8084

    
8085
function array_is_nested($array) {
8086
    foreach ($array as $value) {
8087
        if (is_array($value)) {
8088
            return true;
8089
        }
8090
    }
8091
    return false;
8092
}
8093

    
8094
/**
8095
 *** get_performance_info() pairs up with init_performance_info()
8096
 *** loaded in setup.php. Returns an array with 'html' and 'txt'
8097
 *** values ready for use, and each of the individual stats provided
8098
 *** separately as well.
8099
 ***
8100
 **/
8101
function get_performance_info() {
8102
    global $CFG, $PERF, $rcache;
8103

    
8104
    $info = array();
8105
    $info['html'] = '';         // holds userfriendly HTML representation
8106
    $info['txt']  = me() . ' '; // holds log-friendly representation
8107

    
8108
    $info['realtime'] = microtime_diff($PERF->starttime, microtime());
8109

    
8110
    $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
8111
    $info['txt'] .= 'time: '.$info['realtime'].'s ';
8112

    
8113
    if (function_exists('memory_get_usage')) {
8114
        $info['memory_total'] = memory_get_usage();
8115
        $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
8116
        $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
8117
        $info['txt']  .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
8118
    }
8119

    
8120
    if (function_exists('memory_get_peak_usage')) {
8121
        $info['memory_peak'] = memory_get_peak_usage();
8122
        $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
8123
        $info['txt']  .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
8124
    }
8125

    
8126
    $inc = get_included_files();
8127
    //error_log(print_r($inc,1));
8128
    $info['includecount'] = count($inc);
8129
    $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
8130
    $info['txt']  .= 'includecount: '.$info['includecount'].' ';
8131

    
8132
    if (!empty($PERF->dbqueries)) {
8133
        $info['dbqueries'] = $PERF->dbqueries;
8134
        $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
8135
        $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
8136
    }
8137

    
8138
    if (!empty($PERF->logwrites)) {
8139
        $info['logwrites'] = $PERF->logwrites;
8140
        $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
8141
        $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
8142
    }
8143

    
8144
    if (!empty($PERF->profiling) && $PERF->profiling) {
8145
        require_once($CFG->dirroot .'/lib/profilerlib.php');
8146
        $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
8147
    }
8148

    
8149
    if (function_exists('posix_times')) {
8150
        $ptimes = posix_times();
8151
        if (is_array($ptimes)) {
8152
            foreach ($ptimes as $key => $val) {
8153
                $info[$key] = $ptimes[$key] -  $PERF->startposixtimes[$key];
8154
            }
8155
            $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
8156
            $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
8157
        }
8158
    }
8159

    
8160
    // Grab the load average for the last minute
8161
    // /proc will only work under some linux configurations
8162
    // while uptime is there under MacOSX/Darwin and other unices
8163
    if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
8164
        list($server_load) = explode(' ', $loadavg[0]);
8165
        unset($loadavg);
8166
    } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
8167
        if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
8168
            $server_load = $matches[1];
8169
        } else {
8170
            trigger_error('Could not parse uptime output!');
8171
        }
8172
    }
8173
    if (!empty($server_load)) {
8174
        $info['serverload'] = $server_load;
8175
        $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
8176
        $info['txt'] .= "serverload: {$info['serverload']} ";
8177
    }
8178

    
8179
    if (isset($rcache->hits) && isset($rcache->misses)) {
8180
        $info['rcachehits'] = $rcache->hits;
8181
        $info['rcachemisses'] = $rcache->misses;
8182
        $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
8183
            "{$rcache->hits}/{$rcache->misses}</span> ";
8184
        $info['txt'] .= 'rcache: '.
8185
            "{$rcache->hits}/{$rcache->misses} ";
8186
    }
8187
    $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
8188
    return $info;
8189
}
8190

    
8191
function apd_get_profiling() {
8192
    return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
8193
}
8194

    
8195
/**
8196
 * Delete directory or only it's content
8197
 * @param string $dir directory path
8198
 * @param bool $content_only
8199
 * @return bool success, true also if dir does not exist
8200
 */
8201
function remove_dir($dir, $content_only=false) {
8202
    if (!file_exists($dir)) {
8203
        // nothing to do
8204
        return true;
8205
    }
8206
    $handle = opendir($dir);
8207
    $result = true;
8208
    while (false!==($item = readdir($handle))) {
8209
        if($item != '.' && $item != '..') {
8210
            if(is_dir($dir.'/'.$item)) {
8211
                $result = remove_dir($dir.'/'.$item) && $result;
8212
            }else{
8213
                $result = unlink($dir.'/'.$item) && $result;
8214
            }
8215
        }
8216
    }
8217
    closedir($handle);
8218
    if ($content_only) {
8219
        return $result;
8220
    }
8221
    return rmdir($dir); // if anything left the result will be false, noo need for && $result
8222
}
8223

    
8224
/**
8225
 * Function to check if a directory exists and optionally create it.
8226
 *
8227
 * @param string absolute directory path (must be under $CFG->dataroot)
8228
 * @param boolean create directory if does not exist
8229
 * @param boolean create directory recursively
8230
 *
8231
 * @return boolean true if directory exists or created
8232
 */
8233
function check_dir_exists($dir, $create=false, $recursive=false) {
8234

    
8235
    global $CFG;
8236

    
8237
    if (strstr(cleardoubleslashes($dir), cleardoubleslashes($CFG->dataroot.'/')) === false) {
8238
        debugging('Warning. Wrong call to check_dir_exists(). $dir must be an absolute path under $CFG->dataroot ("' . $dir . '" is incorrect)', DEBUG_DEVELOPER);
8239
    }
8240

    
8241
    $status = true;
8242

    
8243
    if(!is_dir($dir)) {
8244
        if (!$create) {
8245
            $status = false;
8246
        } else {
8247
            umask(0000);
8248
            if ($recursive) {
8249
            /// We are going to make it recursive under $CFG->dataroot only
8250
            /// (will help sites running open_basedir security and others)
8251
                $dir = str_replace(cleardoubleslashes($CFG->dataroot . '/'), '', cleardoubleslashes($dir));
8252
            /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-(
8253
                $dirs = explode('/', $dir); /// Extract path parts
8254
            /// Iterate over each part with start point $CFG->dataroot
8255
                $dir = $CFG->dataroot . '/';
8256
                foreach ($dirs as $part) {
8257
                    if ($part == '') {
8258
                        continue;
8259
                    }
8260
                    $dir .= $part.'/';
8261
                    if (!is_dir($dir)) {
8262
                        if (!mkdir($dir, $CFG->directorypermissions)) {
8263
                            $status = false;
8264
                            break;
8265
                        }
8266
                    }
8267
                }
8268
            } else {
8269
                $status = mkdir($dir, $CFG->directorypermissions);
8270
            }
8271
        }
8272
    }
8273
    return $status;
8274
}
8275

    
8276
function report_session_error() {
8277
    global $CFG, $FULLME;
8278

    
8279
    if (empty($CFG->lang)) {
8280
        $CFG->lang = "en";
8281
    }
8282
    // Set up default theme and locale
8283
    theme_setup();
8284
    moodle_setlocale();
8285

    
8286
    //clear session cookies
8287
    if (check_php_version('5.2.0')) {
8288
        //PHP 5.2.0
8289
        setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8290
        setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8291
    } else {
8292
        setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8293
        setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8294
    }
8295
    //increment database error counters
8296
    if (isset($CFG->session_error_counter)) {
8297
        set_config('session_error_counter', 1 + $CFG->session_error_counter);
8298
    } else {
8299
        set_config('session_error_counter', 1);
8300
    }
8301
    redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
8302
}
8303

    
8304

    
8305
/**
8306
 * Detect if an object or a class contains a given property
8307
 * will take an actual object or the name of a class
8308
 * @param mix $obj Name of class or real object to test
8309
 * @param string $property name of property to find
8310
 * @return bool true if property exists
8311
 */
8312
function object_property_exists( $obj, $property ) {
8313
    if (is_string( $obj )) {
8314
        $properties = get_class_vars( $obj );
8315
    }
8316
    else {
8317
        $properties = get_object_vars( $obj );
8318
    }
8319
    return array_key_exists( $property, $properties );
8320
}
8321

    
8322

    
8323
/**
8324
 * Detect a custom script replacement in the data directory that will
8325
 * replace an existing moodle script
8326
 * @param string $urlpath path to the original script
8327
 * @return string full path name if a custom script exists
8328
 * @return bool false if no custom script exists
8329
 */
8330
function custom_script_path($urlpath='') {
8331
    global $CFG;
8332

    
8333
    // set default $urlpath, if necessary
8334
    if (empty($urlpath)) {
8335
        $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
8336
    }
8337

    
8338
    // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
8339
    if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
8340
        return false;
8341
    }
8342

    
8343
    // replace wwwroot with the path to the customscripts folder and clean path
8344
    $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
8345

    
8346
    // remove the query string, if any
8347
    if (($strpos = strpos($scriptpath, '?')) !== false) {
8348
        $scriptpath = substr($scriptpath, 0, $strpos);
8349
    }
8350

    
8351
    // remove trailing slashes, if any
8352
    $scriptpath = rtrim($scriptpath, '/\\');
8353

    
8354
    // append index.php, if necessary
8355
    if (is_dir($scriptpath)) {
8356
        $scriptpath .= '/index.php';
8357
    }
8358

    
8359
    // check the custom script exists
8360
    if (file_exists($scriptpath)) {
8361
        return $scriptpath;
8362
    } else {
8363
        return false;
8364
    }
8365
}
8366

    
8367
/**
8368
 * Wrapper function to load necessary editor scripts
8369
 * to $CFG->editorsrc array. Params can be coursei id
8370
 * or associative array('courseid' => value, 'name' => 'editorname').
8371
 * @uses $CFG
8372
 * @param mixed $args Courseid or associative array.
8373
 */
8374
function loadeditor($args) {
8375
    global $CFG;
8376
    include($CFG->libdir .'/editorlib.php');
8377
    return editorObject::loadeditor($args);
8378
}
8379

    
8380
/**
8381
 * Returns whether or not the user object is a remote MNET user. This function
8382
 * is in moodlelib because it does not rely on loading any of the MNET code.
8383
 *
8384
 * @param object $user A valid user object
8385
 * @return bool        True if the user is from a remote Moodle.
8386
 */
8387
function is_mnet_remote_user($user) {
8388
    global $CFG;
8389

    
8390
    if (!isset($CFG->mnet_localhost_id)) {
8391
        include_once $CFG->dirroot . '/mnet/lib.php';
8392
        $env = new mnet_environment();
8393
        $env->init();
8394
        unset($env);
8395
    }
8396

    
8397
    return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
8398
}
8399

    
8400
/**
8401
 * Checks if a given plugin is in the list of enabled enrolment plugins.
8402
 *
8403
 * @param string $auth Enrolment plugin.
8404
 * @return boolean Whether the plugin is enabled.
8405
 */
8406
function is_enabled_enrol($enrol='') {
8407
    global $CFG;
8408

    
8409
    // use the global default if not specified
8410
    if ($enrol == '') {
8411
        $enrol = $CFG->enrol;
8412
    }
8413
    return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
8414
}
8415

    
8416
/**
8417
 * This function will search for browser prefereed languages, setting Moodle
8418
 * to use the best one available if $SESSION->lang is undefined
8419
 */
8420
function setup_lang_from_browser() {
8421

    
8422
    global $CFG, $SESSION, $USER;
8423

    
8424
    if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
8425
        // Lang is defined in session or user profile, nothing to do
8426
        return;
8427
    }
8428

    
8429
    if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
8430
        return;
8431
    }
8432

    
8433
/// Extract and clean langs from headers
8434
    $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
8435
    $rawlangs = str_replace('-', '_', $rawlangs);         // we are using underscores
8436
    $rawlangs = explode(',', $rawlangs);                  // Convert to array
8437
    $langs = array();
8438

    
8439
    $order = 1.0;
8440
    foreach ($rawlangs as $lang) {
8441
        if (strpos($lang, ';') === false) {
8442
            $langs[(string)$order] = $lang;
8443
            $order = $order-0.01;
8444
        } else {
8445
            $parts = explode(';', $lang);
8446
            $pos = strpos($parts[1], '=');
8447
            $langs[substr($parts[1], $pos+1)] = $parts[0];
8448
        }
8449
    }
8450
    krsort($langs, SORT_NUMERIC);
8451

    
8452
    $langlist = get_list_of_languages();
8453

    
8454
/// Look for such langs under standard locations
8455
    foreach ($langs as $lang) {
8456
        $lang = strtolower(clean_param($lang.'_utf8', PARAM_SAFEDIR)); // clean it properly for include
8457
        if (!array_key_exists($lang, $langlist)) {
8458
            continue; // language not allowed, try next one
8459
        }
8460
        if (file_exists($CFG->dataroot .'/lang/'. $lang) or file_exists($CFG->dirroot .'/lang/'. $lang)) {
8461
            $SESSION->lang = $lang; /// Lang exists, set it in session
8462
            break; /// We have finished. Go out
8463
        }
8464
    }
8465
    return;
8466
}
8467

    
8468

    
8469
////////////////////////////////////////////////////////////////////////////////
8470

    
8471
function is_newnav($navigation) {
8472
    if (is_array($navigation) && !empty($navigation['newnav'])) {
8473
        return true;
8474
    } else {
8475
        return false;
8476
    }
8477
}
8478

    
8479
/**
8480
 * Checks whether the given variable name is defined as a variable within the given object.
8481
 * @note This will NOT work with stdClass objects, which have no class variables.
8482
 * @param string $var The variable name
8483
 * @param object $object The object to check
8484
 * @return boolean
8485
 */
8486
function in_object_vars($var, $object) {
8487
    $class_vars = get_class_vars(get_class($object));
8488
    $class_vars = array_keys($class_vars);
8489
    return in_array($var, $class_vars);
8490
}
8491

    
8492
/**
8493
 * Returns an array without repeated objects.
8494
 * This function is similar to array_unique, but for arrays that have objects as values
8495
 *
8496
 * @param unknown_type $array
8497
 * @param unknown_type $keep_key_assoc
8498
 * @return unknown
8499
 */
8500
function object_array_unique($array, $keep_key_assoc = true) {
8501
    $duplicate_keys = array();
8502
    $tmp         = array();
8503

    
8504
    foreach ($array as $key=>$val) {
8505
        // convert objects to arrays, in_array() does not support objects
8506
        if (is_object($val)) {
8507
            $val = (array)$val;
8508
        }
8509

    
8510
        if (!in_array($val, $tmp)) {
8511
            $tmp[] = $val;
8512
        } else {
8513
            $duplicate_keys[] = $key;
8514
        }
8515
    }
8516

    
8517
    foreach ($duplicate_keys as $key) {
8518
        unset($array[$key]);
8519
    }
8520

    
8521
    return $keep_key_assoc ? $array : array_values($array);
8522
}
8523

    
8524
/**
8525
 * Returns the language string for the given plugin.
8526
 *
8527
 * @param string $plugin the plugin code name
8528
 * @param string $type the type of plugin (mod, block, filter)
8529
 * @return string The plugin language string
8530
 */
8531
function get_plugin_name($plugin, $type='mod') {
8532
    $plugin_name = '';
8533

    
8534
    switch ($type) {
8535
        case 'mod':
8536
            $plugin_name = get_string('modulename', $plugin);
8537
            break;
8538
        case 'blocks':
8539
            $plugin_name = get_string('blockname', "block_$plugin");
8540
            if (empty($plugin_name) || $plugin_name == '[[blockname]]') {
8541
                if (($block = block_instance($plugin)) !== false) {
8542
                    $plugin_name = $block->get_title();
8543
                } else {
8544
                    $plugin_name = "[[$plugin]]";
8545
                }
8546
            }
8547
            break;
8548
        case 'filter':
8549
            $plugin_name = trim(get_string('filtername', $plugin));
8550
            if (empty($plugin_name) or ($plugin_name == '[[filtername]]')) {
8551
                $textlib = textlib_get_instance();
8552
                $plugin_name = $textlib->strtotitle($plugin);
8553
            }
8554
            break;
8555
        default:
8556
            $plugin_name = $plugin;
8557
            break;
8558
    }
8559

    
8560
    return $plugin_name;
8561
}
8562

    
8563
/**
8564
 * Is a userid the primary administrator?
8565
 *
8566
 * @param $userid int id of user to check
8567
 * @return boolean
8568
 */
8569
function is_primary_admin($userid){
8570
    $primaryadmin =  get_admin();
8571

    
8572
    if($userid == $primaryadmin->id){
8573
        return true;
8574
    }else{
8575
        return false;
8576
    }
8577
}
8578

    
8579
/**
8580
 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
8581
 */
8582
function get_site_identifier() {
8583
    global $CFG;
8584
    // Check to see if it is missing. If so, initialise it.
8585
    if (empty($CFG->siteidentifier)) {
8586
        set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
8587
    }
8588
    // Return it.
8589
    return $CFG->siteidentifier;
8590
}
8591

    
8592
// vim:autoindent:expandtab:shiftwidth=4:tabstop=4:tw=140:
8593
?>