MDL-8323 finished full conversion to proper $COURSE global - no more $CFG->coursethem...
[moodle.git] / lib / moodlelib.php
1 <?php // $Id$
3 ///////////////////////////////////////////////////////////////////////////
4 //                                                                       //
5 // NOTICE OF COPYRIGHT                                                   //
6 //                                                                       //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment         //
8 //          http://moodle.org                                            //
9 //                                                                       //
10 // Copyright (C) 1999-2004  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 ///////////////////////////////////////////////////////////////////////////
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  * @version $Id$
35  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
36  * @package moodlecore
37  */
39 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
41 /**
42  * Used by some scripts to check they are being called by Moodle
43  */
44 define('MOODLE_INTERNAL', true);
46 /**
47  * No groups used?
48  */
49 define('NOGROUPS', 0);
51 /**
52  * Groups used?
53  */
54 define('SEPARATEGROUPS', 1);
56 /**
57  * Groups visible?
58  */
59 define('VISIBLEGROUPS', 2);
61 /// Date and time constants ///
62 /**
63  * Time constant - the number of seconds in a week
64  */
65 define('WEEKSECS', 604800);
67 /**
68  * Time constant - the number of seconds in a day
69  */
70 define('DAYSECS', 86400);
72 /**
73  * Time constant - the number of seconds in an hour
74  */
75 define('HOURSECS', 3600);
77 /**
78  * Time constant - the number of seconds in a minute
79  */
80 define('MINSECS', 60);
82 /**
83  * Time constant - the number of minutes in a day
84  */
85 define('DAYMINS', 1440);
87 /**
88  * Time constant - the number of minutes in an hour
89  */
90 define('HOURMINS', 60);
92 /// Parameter constants - every call to optional_param(), required_param()  ///
93 /// or clean_param() should have a specified type of parameter.  //////////////
95 /**
96  * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
97  * originally was 0, but changed because we need to detect unknown
98  * parameter types and swiched order in clean_param().
99  */
100 define('PARAM_RAW', 666);
102 /**
103  * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
104  * It was one of the first types, that is why it is abused so much ;-)
105  */
106 define('PARAM_CLEAN',    0x0001);
108 /**
109  * PARAM_INT - integers only, use when expecting only numbers.
110  */
111 define('PARAM_INT',      0x0002);
113 /**
114  * PARAM_INTEGER - an alias for PARAM_INT
115  */
116 define('PARAM_INTEGER',  0x0002);
118 /**
119  * PARAM_NUMBER - a real/floating point number. 
120  */
121 define('PARAM_NUMBER',  0x000a);
123 /**
124  * PARAM_ALPHA - contains only english letters.
125  */
126 define('PARAM_ALPHA',    0x0004);
128 /**
129  * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
130  * @TODO: should we alias it to PARAM_ALPHANUM ?
131  */
132 define('PARAM_ACTION',   0x0004);
134 /**
135  * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
136  * @TODO: should we alias it to PARAM_ALPHANUM ?
137  */
138 define('PARAM_FORMAT',   0x0004);
140 /**
141  * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
142  */
143 define('PARAM_NOTAGS',   0x0008);
145  /**
146  * PARAM_MULTILANG - alias of PARAM_TEXT.
147  */
148 define('PARAM_MULTILANG',  0x0009);
150  /**
151  * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
152  */
153 define('PARAM_TEXT',  0x0009);
155 /**
156  * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
157  */
158 define('PARAM_FILE',     0x0010);
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);
166 /**
167  * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
168  */
169 define('PARAM_HOST',     0x0040);
171 /**
172  * PARAM_URL - expected properly formatted URL.
173  */
174 define('PARAM_URL',      0x0080);
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);
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);
187 /**
188  * PARAM_ALPHANUM - expected numbers and letters only.
189  */
190 define('PARAM_ALPHANUM', 0x0400);
192 /**
193  * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
194  */
195 define('PARAM_BOOL',     0x0800);
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);
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);
210 /**
211  * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
212  */
213 define('PARAM_SAFEDIR',  0x4000);
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);
220 /**
221  * PARAM_PEM - Privacy Enhanced Mail format
222  */
223 define('PARAM_PEM',      0x10000);
225 /**
226  * PARAM_BASE64 - Base 64 encoded format
227  */
228 define('PARAM_BASE64',   0x20000);
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');
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 and E_RECOVERABLE_ERROR for now */
245 define ('DEBUG_ALL', 2047);
246 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
247 define ('DEBUG_DEVELOPER', 34815);
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);
260 /// PARAMETER HANDLING ////////////////////////////////////////////////////
262 /**
263  * Returns a particular value for the named variable, taken from
264  * POST or GET.  If the parameter doesn't exist then an error is
265  * thrown because we require this variable.
266  *
267  * This function should be used to initialise all required values
268  * in a script that are based on parameters.  Usually it will be
269  * used like this:
270  *    $id = required_param('id');
271  *
272  * @param string $parname the name of the page parameter we want
273  * @param int $type expected type of parameter
274  * @return mixed
275  */
276 function required_param($parname, $type=PARAM_CLEAN) {
278     // detect_unchecked_vars addition
279     global $CFG;
280     if (!empty($CFG->detect_unchecked_vars)) {
281         global $UNCHECKED_VARS;
282         unset ($UNCHECKED_VARS->vars[$parname]);
283     }
285     if (isset($_POST[$parname])) {       // POST has precedence
286         $param = $_POST[$parname];
287     } else if (isset($_GET[$parname])) {
288         $param = $_GET[$parname];
289     } else {
290         error('A required parameter ('.$parname.') was missing');
291     }
293     return clean_param($param, $type);
296 /**
297  * Returns a particular value for the named variable, taken from
298  * POST or GET, otherwise returning a given default.
299  *
300  * This function should be used to initialise all optional values
301  * in a script that are based on parameters.  Usually it will be
302  * used like this:
303  *    $name = optional_param('name', 'Fred');
304  *
305  * @param string $parname the name of the page parameter we want
306  * @param mixed  $default the default value to return if nothing is found
307  * @param int $type expected type of parameter
308  * @return mixed
309  */
310 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
312     // detect_unchecked_vars addition
313     global $CFG;
314     if (!empty($CFG->detect_unchecked_vars)) {
315         global $UNCHECKED_VARS;
316         unset ($UNCHECKED_VARS->vars[$parname]);
317     }
319     if (isset($_POST[$parname])) {       // POST has precedence
320         $param = $_POST[$parname];
321     } else if (isset($_GET[$parname])) {
322         $param = $_GET[$parname];
323     } else {
324         return $default;
325     }
327     return clean_param($param, $type);
330 /**
331  * Used by {@link optional_param()} and {@link required_param()} to
332  * clean the variables and/or cast to specific types, based on
333  * an options field.
334  * <code>
335  * $course->format = clean_param($course->format, PARAM_ALPHA);
336  * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
337  * </code>
338  *
339  * @uses $CFG
340  * @uses PARAM_CLEAN
341  * @uses PARAM_INT
342  * @uses PARAM_INTEGER
343  * @uses PARAM_ALPHA
344  * @uses PARAM_ALPHANUM
345  * @uses PARAM_NOTAGS
346  * @uses PARAM_ALPHAEXT
347  * @uses PARAM_BOOL
348  * @uses PARAM_SAFEDIR
349  * @uses PARAM_CLEANFILE
350  * @uses PARAM_FILE
351  * @uses PARAM_PATH
352  * @uses PARAM_HOST
353  * @uses PARAM_URL
354  * @uses PARAM_LOCALURL
355  * @uses PARAM_CLEANHTML
356  * @uses PARAM_SEQUENCE
357  * @param mixed $param the variable we are cleaning
358  * @param int $type expected format of param after cleaning.
359  * @return mixed
360  */
361 function clean_param($param, $type) {
363     global $CFG;
365     if (is_array($param)) {              // Let's loop
366         $newparam = array();
367         foreach ($param as $key => $value) {
368             $newparam[$key] = clean_param($value, $type);
369         }
370         return $newparam;
371     }
373     switch ($type) {
374         case PARAM_RAW:          // no cleaning at all
375             return $param;
377         case PARAM_CLEAN:        // General HTML cleaning, try to use more specific type if possible
378             if (is_numeric($param)) {
379                 return $param;
380             }
381             $param = stripslashes($param);   // Needed for kses to work fine
382             $param = clean_text($param);     // Sweep for scripts, etc
383             return addslashes($param);       // Restore original request parameter slashes
385         case PARAM_CLEANHTML:    // prepare html fragment for display, do not store it into db!!
386             $param = stripslashes($param);   // Remove any slashes
387             $param = clean_text($param);     // Sweep for scripts, etc
388             return trim($param);
390         case PARAM_INT:
391             return (int)$param;  // Convert to integer
393         case PARAM_NUMBER:
394             return (float)$param;  // Convert to integer
396         case PARAM_ALPHA:        // Remove everything not a-z
397             return eregi_replace('[^a-zA-Z]', '', $param);
399         case PARAM_ALPHANUM:     // Remove everything not a-zA-Z0-9
400             return eregi_replace('[^A-Za-z0-9]', '', $param);
402         case PARAM_ALPHAEXT:     // Remove everything not a-zA-Z/_-
403             return eregi_replace('[^a-zA-Z/_-]', '', $param);
405         case PARAM_SEQUENCE:     // Remove everything not 0-9,
406             return eregi_replace('[^0-9,]', '', $param);
408         case PARAM_BOOL:         // Convert to 1 or 0
409             $tempstr = strtolower($param);
410             if ($tempstr == 'on' or $tempstr == 'yes' ) {
411                 $param = 1;
412             } else if ($tempstr == 'off' or $tempstr == 'no') {
413                 $param = 0;
414             } else {
415                 $param = empty($param) ? 0 : 1;
416             }
417             return $param;
419         case PARAM_NOTAGS:       // Strip all tags
420             return strip_tags($param);
422         case PARAM_TEXT:    // leave only tags needed for multilang
423             return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
425         case PARAM_SAFEDIR:      // Remove everything not a-zA-Z0-9_-
426             return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
428         case PARAM_CLEANFILE:    // allow only safe characters
429             return clean_filename($param);
431         case PARAM_FILE:         // Strip all suspicious characters from filename
432             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
433             $param = ereg_replace('\.\.+', '', $param);
434             if($param == '.') {
435                 $param = '';
436             }
437             return $param;
439         case PARAM_PATH:         // Strip all suspicious characters from file path
440             $param = str_replace('\\\'', '\'', $param);
441             $param = str_replace('\\"', '"', $param);
442             $param = str_replace('\\', '/', $param);
443             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
444             $param = ereg_replace('\.\.+', '', $param);
445             $param = ereg_replace('//+', '/', $param);
446             return ereg_replace('/(\./)+', '/', $param);
448         case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
449             preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
450             // match ipv4 dotted quad
451             if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
452                 // confirm values are ok
453                 if ( $match[0] > 255
454                      || $match[1] > 255
455                      || $match[3] > 255
456                      || $match[4] > 255 ) {
457                     // hmmm, what kind of dotted quad is this?
458                     $param = '';
459                 }
460             } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
461                        && !preg_match('/^[\.-]/',  $param) // no leading dots/hyphens
462                        && !preg_match('/[\.-]$/',  $param) // no trailing dots/hyphens
463                        ) {
464                 // all is ok - $param is respected
465             } else {
466                 // all is not ok...
467                 $param='';
468             }
469             return $param;
471         case PARAM_URL:          // allow safe ftp, http, mailto urls
472             include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
473             if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
474                 // all is ok, param is respected
475             } else {
476                 $param =''; // not really ok
477             }
478             return $param;
480         case PARAM_LOCALURL:     // allow http absolute, root relative and relative URLs within wwwroot
481             clean_param($param, PARAM_URL);
482             if (!empty($param)) {
483                 if (preg_match(':^/:', $param)) {
484                     // root-relative, ok!
485                 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
486                     // absolute, and matches our wwwroot
487                 } else {
488                     // relative - let's make sure there are no tricks
489                     if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
490                         // looks ok.
491                     } else {
492                         $param = '';
493                     }
494                 }
495             }
496             return $param;
497         case PARAM_PEM:
498             $param = trim($param);
499             // PEM formatted strings may contain letters/numbers and the symbols
500             // forward slash: /
501             // plus sign:     +
502             // equal sign:    =
503             // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
504             if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
505                 list($wholething, $body) = $matches;
506                 unset($wholething, $matches);
507                 $b64 = clean_param($body, PARAM_BASE64);
508                 if (!empty($b64)) {
509                     return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
510                 } else {
511                     return '';
512                 }
513             }
514             return '';
515         case PARAM_BASE64:
516             if (!empty($param)) {
517                 // PEM formatted strings may contain letters/numbers and the symbols
518                 // forward slash: /
519                 // plus sign:     +
520                 // equal sign:    =
521                 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
522                     return '';
523                 }
524                 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
525                 // Each line of base64 encoded data must be 64 characters in
526                 // length, except for the last line which may be less than (or
527                 // equal to) 64 characters long.
528                 for ($i=0, $j=count($lines); $i < $j; $i++) {
529                     if ($i + 1 == $j) {
530                         if (64 < strlen($lines[$i])) {
531                             return '';
532                         }
533                         continue;
534                     }
536                     if (64 != strlen($lines[$i])) {
537                         return '';
538                     }
539                 }
540                 return implode("\n",$lines);
541             } else {
542                 return '';
543             }
544         default:                 // throw error, switched parameters in optional_param or another serious problem
545             error("Unknown parameter type: $type");
546     }
551 /**
552  * Set a key in global configuration
553  *
554  * Set a key/value pair in both this session's {@link $CFG} global variable
555  * and in the 'config' database table for future sessions.
556  *
557  * Can also be used to update keys for plugin-scoped configs in config_plugin table.
558  * In that case it doesn't affect $CFG.
559  *
560  * @param string $name the key to set
561  * @param string $value the value to set (without magic quotes)
562  * @param string $plugin (optional) the plugin scope
563  * @uses $CFG
564  * @return bool
565  */
566 function set_config($name, $value, $plugin=NULL) {
567 /// No need for get_config because they are usually always available in $CFG
569     global $CFG;
571     if (empty($plugin)) {
572         $CFG->$name = $value;  // So it's defined for this invocation at least
574         if (get_field('config', 'name', 'name', $name)) {
575             return set_field('config', 'value', addslashes($value), 'name', $name);
576         } else {
577             $config = new object();
578             $config->name = $name;
579             $config->value = addslashes($value);
580             return insert_record('config', $config);
581         }
582     } else { // plugin scope
583         if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
584             return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
585         } else {
586             $config = new object();
587             $config->plugin = addslashes($plugin);
588             $config->name   = $name;
589             $config->value  = $value;
590             return insert_record('config_plugins', $config);
591         }
592     }
595 /**
596  * Get configuration values from the global config table
597  * or the config_plugins table.
598  *
599  * If called with no parameters it will do the right thing
600  * generating $CFG safely from the database without overwriting
601  * existing values.
602  *
603  * If called with 2 parameters it will return a $string single
604  * value or false of the value is not found.
605  *
606  * @param string $plugin
607  * @param string $name
608  * @uses $CFG
609  * @return hash-like object or single value
610  *
611  */
612 function get_config($plugin=NULL, $name=NULL) {
614     global $CFG;
616     if (!empty($name)) { // the user is asking for a specific value
617         if (!empty($plugin)) {
618             return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
619         } else {
620             return get_field('config', 'value', 'name', $name);
621         }
622     }
624     // the user is after a recordset
625     if (!empty($plugin)) {
626         if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
627             $configs = (array)$configs;
628             $localcfg = array();
629             foreach ($configs as $config) {
630                 $localcfg[$config->name] = $config->value;
631             }
632             return (object)$localcfg;
633         } else {
634             return false;
635         }
636     } else {
637         // this was originally in setup.php
638         if ($configs = get_records('config')) {
639             $localcfg = (array)$CFG;
640             foreach ($configs as $config) {
641                 if (!isset($localcfg[$config->name])) {
642                     $localcfg[$config->name] = $config->value;
643                 } else {
644                     if ($localcfg[$config->name] != $config->value ) {
645                         // complain if the DB has a different
646                         // value than config.php does
647                         error_log("\$CFG->{$config->name} in config.php ({$localcfg[$config->name]}) overrides database setting ({$config->value})");
648                     }
649                 }
650             }
652             $localcfg = (object)$localcfg;
653             return $localcfg;
654         } else {
655             // preserve $CFG if DB returns nothing or error
656             return $CFG;
657         }
659     }
662 /**
663  * Removes a key from global configuration
664  *
665  * @param string $name the key to set
666  * @param string $plugin (optional) the plugin scope
667  * @uses $CFG
668  * @return bool
669  */
670 function unset_config($name, $plugin=NULL) {
672     global $CFG;
674     unset($CFG->$name);
676     if (empty($plugin)) {
677         return delete_records('config', 'name', $name);
678     } else { 
679         return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
680     }
684 /**
685  * Refresh current $USER session global variable with all their current preferences.
686  * @uses $USER
687  */
688 function reload_user_preferences() {
690     global $USER;
692     if(empty($USER) || empty($USER->id)) {
693         return false;
694     }
696     unset($USER->preference);
698     if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
699         foreach ($preferences as $preference) {
700             $USER->preference[$preference->name] = $preference->value;
701         }
702     } else {
703             //return empty preference array to hold new values
704             $USER->preference = array();
705     }
708 /**
709  * Sets a preference for the current user
710  * Optionally, can set a preference for a different user object
711  * @uses $USER
712  * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
714  * @param string $name The key to set as preference for the specified user
715  * @param string $value The value to set forthe $name key in the specified user's record
716  * @param int $userid A moodle user ID
717  * @return bool
718  */
719 function set_user_preference($name, $value, $otheruser=NULL) {
721     global $USER;
723     if (empty($otheruser)){
724         if (!empty($USER) && !empty($USER->id)) {
725             $userid = $USER->id;
726         } else {
727             return false;
728         }
729     } else {
730         $userid = $otheruser;
731     }
733     if (empty($name)) {
734         return false;
735     }
737     if ($preference = get_record('user_preferences', 'userid', $userid, 'name', $name)) {
738         if (set_field('user_preferences', 'value', $value, 'id', $preference->id)) {
739             if ($userid == $USER->id) {
740                 $USER->preference[$name] = $value;
741             }
742             return true;
743         } else {
744             return false;
745         }
747     } else {
748         $preference->userid = $userid;
749         $preference->name   = $name;
750         $preference->value  = (string)$value;
751         if (insert_record('user_preferences', $preference)) {
752             if ($userid == $USER->id) {
753                 $USER->preference[$name] = $value;
754             }
755             return true;
756         } else {
757             return false;
758         }
759     }
762 /**
763  * Unsets a preference completely by deleting it from the database
764  * Optionally, can set a preference for a different user id
765  * @uses $USER
766  * @param string  $name The key to unset as preference for the specified user
767  * @param int $userid A moodle user ID
768  * @return bool
769  */
770 function unset_user_preference($name, $userid=NULL) {
772     global $USER;
774     if (empty($userid)){
775         if(!empty($USER) && !empty($USER->id)) {
776             $userid = $USER->id;
777         }
778         else {
779             return false;
780         }
781     }
783     //Delete the preference from $USER
784     if (isset($USER->preference[$name])) {
785         unset($USER->preference[$name]);
786     }
788     //Then from DB
789     return delete_records('user_preferences', 'userid', $userid, 'name', $name);
793 /**
794  * Sets a whole array of preferences for the current user
795  * @param array $prefarray An array of key/value pairs to be set
796  * @param int $userid A moodle user ID
797  * @return bool
798  */
799 function set_user_preferences($prefarray, $userid=NULL) {
801     global $USER;
803     if (!is_array($prefarray) or empty($prefarray)) {
804         return false;
805     }
807     if (empty($userid)){
808         if (!empty($USER) && !empty($USER->id)) {
809             $userid = NULL;  // Continue with the current user below
810         } else {
811             return false;    // No-one to set!
812         }
813     }
815     $return = true;
816     foreach ($prefarray as $name => $value) {
817         // The order is important; if the test for return is done first, then
818         // if one function call fails all the remaining ones will be "optimized away"
819         $return = set_user_preference($name, $value, $userid) and $return;
820     }
821     return $return;
824 /**
825  * If no arguments are supplied this function will return
826  * all of the current user preferences as an array.
827  * If a name is specified then this function
828  * attempts to return that particular preference value.  If
829  * none is found, then the optional value $default is returned,
830  * otherwise NULL.
831  * @param string $name Name of the key to use in finding a preference value
832  * @param string $default Value to be returned if the $name key is not set in the user preferences
833  * @param int $userid A moodle user ID
834  * @uses $USER
835  * @return string
836  */
837 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
839     global $USER;
841     if (empty($userid)) {   // assume current user
842         if (empty($USER->preference)) {
843             return $default;              // Default value (or NULL)
844         }
845         if (empty($name)) {
846             return $USER->preference;     // Whole array
847         }
848         if (!isset($USER->preference[$name])) {
849             return $default;              // Default value (or NULL)
850         }
851         return $USER->preference[$name];  // The single value
853     } else {
854         $preference = get_records_menu('user_preferences', 'userid', $userid, 'name', 'name,value');
856         if (empty($name)) {
857             return $preference;
858         }
859         if (!isset($preference[$name])) {
860             return $default;              // Default value (or NULL)
861         }
862         return $preference[$name];        // The single value
863     }
867 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
869 /**
870  * Given date parts in user time produce a GMT timestamp.
871  *
872  * @param int $year The year part to create timestamp of
873  * @param int $month The month part to create timestamp of
874  * @param int $day The day part to create timestamp of
875  * @param int $hour The hour part to create timestamp of
876  * @param int $minute The minute part to create timestamp of
877  * @param int $second The second part to create timestamp of
878  * @param float $timezone ?
879  * @param bool $applydst ?
880  * @return int timestamp
881  * @todo Finish documenting this function
882  */
883 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
885     $timezone = get_user_timezone_offset($timezone);
887     if (abs($timezone) > 13) {
888         $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
889     } else {
890         $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
891         $time = usertime($time, $timezone);
892         if($applydst) {
893             $time -= dst_offset_on($time);
894         }
895     }
897     return $time;
901 /**
902  * Given an amount of time in seconds, returns string
903  * formatted nicely as months, days, hours etc as needed
904  *
905  * @uses MINSECS
906  * @uses HOURSECS
907  * @uses DAYSECS
908  * @param int $totalsecs ?
909  * @param array $str ?
910  * @return string
911  * @todo Finish documenting this function
912  */
913  function format_time($totalsecs, $str=NULL) {
915     $totalsecs = abs($totalsecs);
917     if (!$str) {  // Create the str structure the slow way
918         $str->day   = get_string('day');
919         $str->days  = get_string('days');
920         $str->hour  = get_string('hour');
921         $str->hours = get_string('hours');
922         $str->min   = get_string('min');
923         $str->mins  = get_string('mins');
924         $str->sec   = get_string('sec');
925         $str->secs  = get_string('secs');
926     }
928     $days      = floor($totalsecs/DAYSECS);
929     $remainder = $totalsecs - ($days*DAYSECS);
930     $hours     = floor($remainder/HOURSECS);
931     $remainder = $remainder - ($hours*HOURSECS);
932     $mins      = floor($remainder/MINSECS);
933     $secs      = $remainder - ($mins*MINSECS);
935     $ss = ($secs == 1)  ? $str->sec  : $str->secs;
936     $sm = ($mins == 1)  ? $str->min  : $str->mins;
937     $sh = ($hours == 1) ? $str->hour : $str->hours;
938     $sd = ($days == 1)  ? $str->day  : $str->days;
940     $odays = '';
941     $ohours = '';
942     $omins = '';
943     $osecs = '';
945     if ($days)  $odays  = $days .' '. $sd;
946     if ($hours) $ohours = $hours .' '. $sh;
947     if ($mins)  $omins  = $mins .' '. $sm;
948     if ($secs)  $osecs  = $secs .' '. $ss;
950     if ($days)  return $odays .' '. $ohours;
951     if ($hours) return $ohours .' '. $omins;
952     if ($mins)  return $omins .' '. $osecs;
953     if ($secs)  return $osecs;
954     return get_string('now');
957 /**
958  * Returns a formatted string that represents a date in user time
959  * <b>WARNING: note that the format is for strftime(), not date().</b>
960  * Because of a bug in most Windows time libraries, we can't use
961  * the nicer %e, so we have to use %d which has leading zeroes.
962  * A lot of the fuss in the function is just getting rid of these leading
963  * zeroes as efficiently as possible.
964  *
965  * If parameter fixday = true (default), then take off leading
966  * zero from %d, else mantain it.
967  *
968  * @uses HOURSECS
969  * @param  int $date timestamp in GMT
970  * @param string $format strftime format
971  * @param float $timezone
972  * @param bool $fixday If true (default) then the leading
973  * zero from %d is removed. If false then the leading zero is mantained.
974  * @return string
975  */
976 function userdate($date, $format='', $timezone=99, $fixday = true) {
978     global $CFG;
980     static $strftimedaydatetime;
982     if ($format == '') {
983         if (empty($strftimedaydatetime)) {
984             $strftimedaydatetime = get_string('strftimedaydatetime');
985         }
986         $format = $strftimedaydatetime;
987     }
989     if (!empty($CFG->nofixday)) {  // Config.php can force %d not to be fixed.
990         $fixday = false;
991     } else if ($fixday) {
992         $formatnoday = str_replace('%d', 'DD', $format);
993         $fixday = ($formatnoday != $format);
994     }
996     $date += dst_offset_on($date);
998     $timezone = get_user_timezone_offset($timezone);
1000     if (abs($timezone) > 13) {   /// Server time
1001         if ($fixday) {
1002             $datestring = strftime($formatnoday, $date);
1003             $daystring  = str_replace(' 0', '', strftime(' %d', $date));
1004             $datestring = str_replace('DD', $daystring, $datestring);
1005         } else {
1006             $datestring = strftime($format, $date);
1007         }
1008     } else {
1009         $date += (int)($timezone * 3600);
1010         if ($fixday) {
1011             $datestring = gmstrftime($formatnoday, $date);
1012             $daystring  = str_replace(' 0', '', gmstrftime(' %d', $date));
1013             $datestring = str_replace('DD', $daystring, $datestring);
1014         } else {
1015             $datestring = gmstrftime($format, $date);
1016         }
1017     }
1019 /// If we are running under Windows convert from windows encoding to UTF-8
1020 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1022    if ($CFG->ostype == 'WINDOWS') {
1023        if ($localewincharset = get_string('localewincharset')) {
1024            $textlib = textlib_get_instance();
1025            $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1026        }
1027    }
1029     return $datestring;
1032 /**
1033  * Given a $time timestamp in GMT (seconds since epoch),
1034  * returns an array that represents the date in user time
1035  *
1036  * @uses HOURSECS
1037  * @param int $time Timestamp in GMT
1038  * @param float $timezone ?
1039  * @return array An array that represents the date in user time
1040  * @todo Finish documenting this function
1041  */
1042 function usergetdate($time, $timezone=99) {
1044     $timezone = get_user_timezone_offset($timezone);
1046     if (abs($timezone) > 13) {    // Server time
1047         return getdate($time);
1048     }
1050     // There is no gmgetdate so we use gmdate instead
1051     $time += dst_offset_on($time);
1052     $time += intval((float)$timezone * HOURSECS);
1054     $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
1056     list(
1057         $getdate['seconds'],
1058         $getdate['minutes'],
1059         $getdate['hours'],
1060         $getdate['mday'],
1061         $getdate['mon'],
1062         $getdate['year'],
1063         $getdate['wday'],
1064         $getdate['yday'],
1065         $getdate['weekday'],
1066         $getdate['month']
1067     ) = explode('_', $datestring);
1069     return $getdate;
1072 /**
1073  * Given a GMT timestamp (seconds since epoch), offsets it by
1074  * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1075  *
1076  * @uses HOURSECS
1077  * @param  int $date Timestamp in GMT
1078  * @param float $timezone
1079  * @return int
1080  */
1081 function usertime($date, $timezone=99) {
1083     $timezone = get_user_timezone_offset($timezone);
1085     if (abs($timezone) > 13) {
1086         return $date;
1087     }
1088     return $date - (int)($timezone * HOURSECS);
1091 /**
1092  * Given a time, return the GMT timestamp of the most recent midnight
1093  * for the current user.
1094  *
1095  * @param int $date Timestamp in GMT
1096  * @param float $timezone ?
1097  * @return ?
1098  */
1099 function usergetmidnight($date, $timezone=99) {
1101     $timezone = get_user_timezone_offset($timezone);
1102     $userdate = usergetdate($date, $timezone);
1104     // Time of midnight of this user's day, in GMT
1105     return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1109 /**
1110  * Returns a string that prints the user's timezone
1111  *
1112  * @param float $timezone The user's timezone
1113  * @return string
1114  */
1115 function usertimezone($timezone=99) {
1117     $tz = get_user_timezone($timezone);
1119     if (!is_float($tz)) {
1120         return $tz;
1121     }
1123     if(abs($tz) > 13) { // Server time
1124         return get_string('serverlocaltime');
1125     }
1127     if($tz == intval($tz)) {
1128         // Don't show .0 for whole hours
1129         $tz = intval($tz);
1130     }
1132     if($tz == 0) {
1133         return 'GMT';
1134     }
1135     else if($tz > 0) {
1136         return 'GMT+'.$tz;
1137     }
1138     else {
1139         return 'GMT'.$tz;
1140     }
1144 /**
1145  * Returns a float which represents the user's timezone difference from GMT in hours
1146  * Checks various settings and picks the most dominant of those which have a value
1147  *
1148  * @uses $CFG
1149  * @uses $USER
1150  * @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
1151  * @return int
1152  */
1153 function get_user_timezone_offset($tz = 99) {
1155     global $USER, $CFG;
1157     $tz = get_user_timezone($tz);
1159     if (is_float($tz)) {
1160         return $tz;
1161     } else {
1162         $tzrecord = get_timezone_record($tz);
1163         if (empty($tzrecord)) {
1164             return 99.0;
1165         }
1166         return (float)$tzrecord->gmtoff / HOURMINS;
1167     }
1170 /**
1171  * Returns a float or a string which denotes the user's timezone
1172  * 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)
1173  * means that for this timezone there are also DST rules to be taken into account
1174  * Checks various settings and picks the most dominant of those which have a value
1175  *
1176  * @uses $USER
1177  * @uses $CFG
1178  * @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
1179  * @return mixed
1180  */
1181 function get_user_timezone($tz = 99) {
1182     global $USER, $CFG;
1184     $timezones = array(
1185         $tz,
1186         isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1187         isset($USER->timezone) ? $USER->timezone : 99,
1188         isset($CFG->timezone) ? $CFG->timezone : 99,
1189         );
1191     $tz = 99;
1193     while(($tz == '' || $tz == 99) && $next = each($timezones)) {
1194         $tz = $next['value'];
1195     }
1197     return is_numeric($tz) ? (float) $tz : $tz;
1200 /**
1201  * ?
1202  *
1203  * @uses $CFG
1204  * @uses $db
1205  * @param string $timezonename ?
1206  * @return object
1207  */
1208 function get_timezone_record($timezonename) {
1209     global $CFG, $db;
1210     static $cache = NULL;
1212     if ($cache === NULL) {
1213         $cache = array();
1214     }
1216     if (isset($cache[$timezonename])) {
1217         return $cache[$timezonename];
1218     }
1220     return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1221                                       WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1224 /**
1225  * ?
1226  *
1227  * @uses $CFG
1228  * @uses $USER
1229  * @param ? $fromyear ?
1230  * @param ? $to_year ?
1231  * @return bool
1232  */
1233 function calculate_user_dst_table($from_year = NULL, $to_year = NULL) {
1234     global $CFG, $SESSION;
1236     $usertz = get_user_timezone();
1238     if (is_float($usertz)) {
1239         // Trivial timezone, no DST
1240         return false;
1241     }
1243     if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1244         // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1245         unset($SESSION->dst_offsets);
1246         unset($SESSION->dst_range);
1247     }
1249     if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1250         // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1251         // This will be the return path most of the time, pretty light computationally
1252         return true;
1253     }
1255     // Reaching here means we either need to extend our table or create it from scratch
1257     // Remember which TZ we calculated these changes for
1258     $SESSION->dst_offsettz = $usertz;
1260     if(empty($SESSION->dst_offsets)) {
1261         // If we 're creating from scratch, put the two guard elements in there
1262         $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1263     }
1264     if(empty($SESSION->dst_range)) {
1265         // If creating from scratch
1266         $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1267         $to   = min((empty($to_year)   ? intval(date('Y')) + 3 : $to_year),   2035);
1269         // Fill in the array with the extra years we need to process
1270         $yearstoprocess = array();
1271         for($i = $from; $i <= $to; ++$i) {
1272             $yearstoprocess[] = $i;
1273         }
1275         // Take note of which years we have processed for future calls
1276         $SESSION->dst_range = array($from, $to);
1277     }
1278     else {
1279         // If needing to extend the table, do the same
1280         $yearstoprocess = array();
1282         $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1283         $to   = min((empty($to_year)   ? $SESSION->dst_range[1] : $to_year),   2035);
1285         if($from < $SESSION->dst_range[0]) {
1286             // Take note of which years we need to process and then note that we have processed them for future calls
1287             for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1288                 $yearstoprocess[] = $i;
1289             }
1290             $SESSION->dst_range[0] = $from;
1291         }
1292         if($to > $SESSION->dst_range[1]) {
1293             // Take note of which years we need to process and then note that we have processed them for future calls
1294             for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1295                 $yearstoprocess[] = $i;
1296             }
1297             $SESSION->dst_range[1] = $to;
1298         }
1299     }
1301     if(empty($yearstoprocess)) {
1302         // This means that there was a call requesting a SMALLER range than we have already calculated
1303         return true;
1304     }
1306     // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1307     // Also, the array is sorted in descending timestamp order!
1309     // Get DB data
1310     $presetrecords = get_records('timezone', 'name', $usertz, 'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, std_startday, std_weekday, std_skipweeks, std_time');
1311     if(empty($presetrecords)) {
1312         return false;
1313     }
1315     // Remove ending guard (first element of the array)
1316     reset($SESSION->dst_offsets);
1317     unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1319     // Add all required change timestamps
1320     foreach($yearstoprocess as $y) {
1321         // Find the record which is in effect for the year $y
1322         foreach($presetrecords as $year => $preset) {
1323             if($year <= $y) {
1324                 break;
1325             }
1326         }
1328         $changes = dst_changes_for_year($y, $preset);
1330         if($changes === NULL) {
1331             continue;
1332         }
1333         if($changes['dst'] != 0) {
1334             $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1335         }
1336         if($changes['std'] != 0) {
1337             $SESSION->dst_offsets[$changes['std']] = 0;
1338         }
1339     }
1341     // Put in a guard element at the top
1342     $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1343     $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1345     // Sort again
1346     krsort($SESSION->dst_offsets);
1348     return true;
1351 function dst_changes_for_year($year, $timezone) {
1353     if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1354         return NULL;
1355     }
1357     $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1358     $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1360     list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1361     list($std_hour, $std_min) = explode(':', $timezone->std_time);
1363     $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1364     $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1366     // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1367     // This has the advantage of being able to have negative values for hour, i.e. for timezones
1368     // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1370     $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1371     $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1373     return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1376 // $time must NOT be compensated at all, it has to be a pure timestamp
1377 function dst_offset_on($time) {
1378     global $SESSION;
1380     if(!calculate_user_dst_table() || empty($SESSION->dst_offsets)) {
1381         return 0;
1382     }
1384     reset($SESSION->dst_offsets);
1385     while(list($from, $offset) = each($SESSION->dst_offsets)) {
1386         if($from <= $time) {
1387             break;
1388         }
1389     }
1391     // This is the normal return path
1392     if($offset !== NULL) {
1393         return $offset;
1394     }
1396     // Reaching this point means we haven't calculated far enough, do it now:
1397     // Calculate extra DST changes if needed and recurse. The recursion always
1398     // moves toward the stopping condition, so will always end.
1400     if($from == 0) {
1401         // We need a year smaller than $SESSION->dst_range[0]
1402         if($SESSION->dst_range[0] == 1971) {
1403             return 0;
1404         }
1405         calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL);
1406         return dst_offset_on($time);
1407     }
1408     else {
1409         // We need a year larger than $SESSION->dst_range[1]
1410         if($SESSION->dst_range[1] == 2035) {
1411             return 0;
1412         }
1413         calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5);
1414         return dst_offset_on($time);
1415     }
1418 function find_day_in_month($startday, $weekday, $month, $year) {
1420     $daysinmonth = days_in_month($month, $year);
1422     if($weekday == -1) {
1423         // Don't care about weekday, so return:
1424         //    abs($startday) if $startday != -1
1425         //    $daysinmonth otherwise
1426         return ($startday == -1) ? $daysinmonth : abs($startday);
1427     }
1429     // From now on we 're looking for a specific weekday
1431     // Give "end of month" its actual value, since we know it
1432     if($startday == -1) {
1433         $startday = -1 * $daysinmonth;
1434     }
1436     // Starting from day $startday, the sign is the direction
1438     if($startday < 1) {
1440         $startday = abs($startday);
1441         $lastmonthweekday  = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1443         // This is the last such weekday of the month
1444         $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1445         if($lastinmonth > $daysinmonth) {
1446             $lastinmonth -= 7;
1447         }
1449         // Find the first such weekday <= $startday
1450         while($lastinmonth > $startday) {
1451             $lastinmonth -= 7;
1452         }
1454         return $lastinmonth;
1456     }
1457     else {
1459         $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1461         $diff = $weekday - $indexweekday;
1462         if($diff < 0) {
1463             $diff += 7;
1464         }
1466         // This is the first such weekday of the month equal to or after $startday
1467         $firstfromindex = $startday + $diff;
1469         return $firstfromindex;
1471     }
1474 /**
1475  * Calculate the number of days in a given month
1476  *
1477  * @param int $month The month whose day count is sought
1478  * @param int $year The year of the month whose day count is sought
1479  * @return int
1480  */
1481 function days_in_month($month, $year) {
1482    return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1485 /**
1486  * Calculate the position in the week of a specific calendar day
1487  *
1488  * @param int $day The day of the date whose position in the week is sought
1489  * @param int $month The month of the date whose position in the week is sought
1490  * @param int $year The year of the date whose position in the week is sought
1491  * @return int
1492  */
1493 function dayofweek($day, $month, $year) {
1494     // I wonder if this is any different from
1495     // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1496     return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1499 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1501 /**
1502  * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1503  * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1504  * sesskey string if $USER exists, or boolean false if not.
1505  *
1506  * @uses $USER
1507  * @return string
1508  */
1509 function sesskey() {
1510     global $USER;
1512     if(!isset($USER)) {
1513         return false;
1514     }
1516     if (empty($USER->sesskey)) {
1517         $USER->sesskey = random_string(10);
1518     }
1520     return $USER->sesskey;
1524 /**
1525  * For security purposes, this function will check that the currently
1526  * given sesskey (passed as a parameter to the script or this function)
1527  * matches that of the current user.
1528  *
1529  * @param string $sesskey optionally provided sesskey
1530  * @return bool
1531  */
1532 function confirm_sesskey($sesskey=NULL) {
1533     global $USER;
1535     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1536         return true;
1537     }
1539     if (empty($sesskey)) {
1540         $sesskey = required_param('sesskey', PARAM_RAW);  // Check script parameters
1541     }
1543     if (!isset($USER->sesskey)) {
1544         return false;
1545     }
1547     return ($USER->sesskey === $sesskey);
1550 /**
1551  * Setup all global $CFG course variables, set locale and also themes
1552  * This function can be used on pages that do not require login instead of require_login()
1553  *
1554  * @param mixed $courseorid id of the course or course object
1555  */
1556 function course_setup($courseorid=0) {
1557     global $COURSE, $CFG, $SITE, $USER;
1559 /// Redefine global $COURSE if needed
1560     if (empty($courseorid)) {
1561         // no change in global $COURSE - for backwards compatibiltiy
1562         // if require_rogin() used after require_login($courseid); 
1563     } else if (is_object($courseorid)) {
1564         $COURSE = clone($courseorid);
1565     } else {
1566         global $course; // used here only to prevent repeated fetching from DB - may be removed later
1567         if (!empty($course->id) and $course->id == SITEID) {
1568             $COURSE = clone($SITE);
1569         } else if (!empty($course->id) and $course->id == $courseorid) {
1570             $COURSE = clone($course);
1571         } else {
1572             if (!$COURSE = get_record('course', 'id', $courseorid)) {
1573                 error('Invalid course ID');
1574             }
1575         }
1576     }
1578 /// set locale and themes
1579     moodle_setlocale();
1580     theme_setup();
1584 /**
1585  * This function checks that the current user is logged in and has the
1586  * required privileges
1587  *
1588  * This function checks that the current user is logged in, and optionally
1589  * whether they are allowed to be in a particular course and view a particular
1590  * course module.
1591  * If they are not logged in, then it redirects them to the site login unless
1592  * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1593  * case they are automatically logged in as guests.
1594  * If $courseid is given and the user is not enrolled in that course then the
1595  * user is redirected to the course enrolment page.
1596  * If $cm is given and the coursemodule is hidden and the user is not a teacher
1597  * in the course then the user is redirected to the course home page.
1598  *
1599  * @uses $CFG
1600  * @uses $SESSION
1601  * @uses $USER
1602  * @uses $FULLME
1603  * @uses SITEID
1604  * @uses $COURSE
1605  * @param mixed $courseorid id of the course or course object
1606  * @param bool $autologinguest
1607  * @param object $cm course module object
1608  */
1609 function require_login($courseorid=0, $autologinguest=true, $cm=null) {
1611     global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1613 /// setup global $COURSE, themes, language and locale
1614     course_setup($courseorid);
1616 /// If the user is not even logged in yet then make sure they are
1617     if (!isloggedin()) {
1618         //NOTE: $USER->site check was obsoleted by session test cookie,
1619         //      $USER->confirmed test is in login/index.php
1620         $SESSION->wantsurl = $FULLME;
1621         if (!empty($_SERVER['HTTP_REFERER'])) {
1622             $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
1623         }
1624         if ($autologinguest and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1625             $loginguest = '?loginguest=true';
1626         } else {
1627             $loginguest = '';
1628         }
1629         if (empty($CFG->loginhttps) or $autologinguest) { //do not require https for guest logins
1630             redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1631         } else {
1632             $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1633             redirect($wwwroot .'/login/index.php');
1634         }
1635         exit;
1636     }
1638 /// check whether the user should be changing password
1639     $userauth = get_auth_plugin($USER->auth);
1640     if (!empty($USER->preference['auth_forcepasswordchange'])){
1641         if ($userauth->can_change_password()) {
1642             $SESSION->wantsurl = $FULLME;
1643             if (empty($CFG->loginhttps)) {
1644                 redirect($CFG->wwwroot .'/login/change_password.php');
1645             } else {
1646                 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1647                 redirect($wwwroot .'/login/change_password.php');
1648             }
1649         } else if($userauth->change_password_url()) {
1650             redirect($userauth->change_password_url());
1651         } else {
1652             error('You cannot proceed without changing your password.
1653                    However there is no available page for changing it.
1654                    Please contact your Moodle Administrator.');
1655         }
1656     }
1658 /// Check that the user account is properly set up
1659     if (user_not_fully_set_up($USER)) {
1660         $SESSION->wantsurl = $FULLME;
1661         redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1662     }
1664 /// Make sure current IP matches the one for this session (if required)
1665     if (!empty($CFG->tracksessionip)) {
1666         if ($USER->sessionIP != md5(getremoteaddr())) {
1667             error(get_string('sessionipnomatch', 'error'));
1668         }
1669     }
1671 /// Make sure the USER has a sesskey set up.  Used for checking script parameters.
1672     sesskey();
1674     // Check that the user has agreed to a site policy if there is one
1675     if (!empty($CFG->sitepolicy)) {
1676         if (!$USER->policyagreed) {
1677             $SESSION->wantsurl = $FULLME;
1678             redirect($CFG->wwwroot .'/user/policy.php');
1679         }
1680     }
1682 /// If the site is currently under maintenance, then print a message
1683     if (!has_capability('moodle/site:config',get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1684         if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1685             print_maintenance_message();
1686             exit;
1687         }
1688     }
1691     if ($COURSE->id == SITEID) {
1692 /// We can eliminate hidden site activities straight away
1693         if (!empty($cm) && !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', 
1694                                                       get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1695             redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
1696         }
1697         return;
1699     } else { 
1700 /// Check if the user can be in a particular course
1701         if (!$context = get_context_instance(CONTEXT_COURSE, $COURSE->id)) {
1702             print_error('nocontext');
1703         }
1705         if (empty($USER->switchrole[$context->id]) &&
1706             !($COURSE->visible && course_parent_visible($COURSE)) &&
1707                !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $COURSE->id)) ){
1708             print_header_simple();
1709             notice(get_string('coursehidden'), $CFG->wwwroot .'/');
1710         }    
1711         
1712     /// Non-guests who don't currently have access, check if they can be allowed in as a guest
1714         if ($USER->username != 'guest' and !has_capability('moodle/course:view', $context)) {
1715             if ($COURSE->guest == 1) {
1716                  // Temporarily assign them guest role for this context,
1717                  // if it fails user is asked to enrol
1718                  load_guest_role($context);
1719             }
1720         }
1722     /// If the user is a guest then treat them according to the course policy about guests
1724         if (has_capability('moodle/legacy:guest', $context, NULL, false)) {
1725             switch ($COURSE->guest) {    /// Check course policy about guest access
1727                 case 1:    /// Guests always allowed 
1728                     if (!has_capability('moodle/course:view', $context)) {    // Prohibited by capability
1729                         print_header_simple();
1730                         notice(get_string('guestsnotallowed', '', $COURSE->fullname), "$CFG->wwwroot/login/index.php");
1731                     }
1732                     if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
1733                         redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, 
1734                                  get_string('activityiscurrentlyhidden'));
1735                     }
1737                     return;   // User is allowed to see this course
1739                     break;
1741                 case 2:    /// Guests allowed with key 
1742                     if (!empty($USER->enrolkey[$COURSE->id])) {   // Set by enrol/manual/enrol.php
1743                         return true;
1744                     }
1745                     //  otherwise drop through to logic below (--> enrol.php)
1746                     break;
1748                 default:    /// Guests not allowed
1749                     print_header_simple('', '', get_string('loggedinasguest'));
1750                     if (empty($USER->switchrole[$context->id])) {  // Normal guest
1751                         notice(get_string('guestsnotallowed', '', $COURSE->fullname), "$CFG->wwwroot/login/index.php");
1752                     } else {
1753                         notify(get_string('guestsnotallowed', '', $COURSE->fullname));
1754                         echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
1755                         print_footer($COURSE);
1756                         exit;
1757                     }
1758                     break;
1759             }
1761     /// For non-guests, check if they have course view access
1763         } else if (has_capability('moodle/course:view', $context)) {
1764             if (!empty($USER->realuser)) {   // Make sure the REAL person can also access this course
1765                 if (!has_capability('moodle/course:view', $context, $USER->realuser)) {
1766                     print_header_simple();
1767                     notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
1768                 }
1769             }
1771         /// Make sure they can read this activity too, if specified
1773             if (!empty($cm) and !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) { 
1774                 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1775             }
1776             return;   // User is allowed to see this course
1778         }
1781     /// Currently not enrolled in the course, so see if they want to enrol
1782         $SESSION->wantsurl = $FULLME;
1783         redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
1784         die;
1785     }
1790 /**
1791  * This function just makes sure a user is logged out.
1792  *
1793  * @uses $CFG
1794  * @uses $USER
1795  */
1796 function require_logout() {
1798     global $USER, $CFG, $SESSION;
1800     if (isset($USER) and isset($USER->id)) {
1801         add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
1803         if ($USER->auth == 'cas' && !empty($CFG->cas_enabled)) {
1804             require($CFG->dirroot.'/auth/cas/logout.php');
1805         }
1806         
1807         if (extension_loaded('openssl')) {
1808             require($CFG->dirroot.'/auth/mnet/auth.php');
1809             $authplugin = new auth_plugin_mnet();
1810             $authplugin->logout();
1811         }
1812     }
1814     if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
1815         // This method is just to try to avoid silly warnings from PHP 4.3.0
1816         session_unregister("USER");
1817         session_unregister("SESSION");
1818     }
1820     setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath);
1821     unset($_SESSION['USER']);
1822     unset($_SESSION['SESSION']);
1824     unset($SESSION);
1825     unset($USER);
1829 /**
1830  * This is a weaker version of {@link require_login()} which only requires login
1831  * when called from within a course rather than the site page, unless
1832  * the forcelogin option is turned on.
1833  *
1834  * @uses $CFG
1835  * @param mixed $courseorid The course object or id in question
1836  * @param bool $autologinguest Allow autologin guests if that is wanted
1837  * @param object $cm Course activity module if known
1838  */
1839 function require_course_login($courseorid, $autologinguest=true, $cm=null) {
1840     global $CFG;
1841     if (!empty($CFG->forcelogin)) {
1842         // login required for both SITE and courses
1843         require_login($courseorid, $autologinguest, $cm);
1844     } else if ((is_object($courseorid) and $courseorid->id == SITEID) or $courseorid == SITEID) {
1845         //login for SITE not required
1846     } else {
1847         // course login always required
1848         require_login($courseorid, $autologinguest, $cm);
1849     }
1852 /**
1853  * Modify the user table by setting the currently logged in user's
1854  * last login to now.
1855  *
1856  * @uses $USER
1857  * @return bool
1858  */
1859 function update_user_login_times() {
1860     global $USER;
1862     $user = new object();
1863     $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
1864     $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
1866     $user->id = $USER->id;
1868     return update_record('user', $user);
1871 /**
1872  * Determines if a user has completed setting up their account.
1873  *
1874  * @param user $user A {@link $USER} object to test for the existance of a valid name and email
1875  * @return bool
1876  */
1877 function user_not_fully_set_up($user) {
1878     return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
1881 function over_bounce_threshold($user) {
1883     global $CFG;
1885     if (empty($CFG->handlebounces)) {
1886         return false;
1887     }
1888     // set sensible defaults
1889     if (empty($CFG->minbounces)) {
1890         $CFG->minbounces = 10;
1891     }
1892     if (empty($CFG->bounceratio)) {
1893         $CFG->bounceratio = .20;
1894     }
1895     $bouncecount = 0;
1896     $sendcount = 0;
1897     if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1898         $bouncecount = $bounce->value;
1899     }
1900     if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1901         $sendcount = $send->value;
1902     }
1903     return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
1906 /**
1907  * @param $user - object containing an id
1908  * @param $reset - will reset the count to 0
1909  */
1910 function set_send_count($user,$reset=false) {
1911     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1912         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1913         update_record('user_preferences',$pref);
1914     }
1915     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1916         // make a new one
1917         $pref->name = 'email_send_count';
1918         $pref->value = 1;
1919         $pref->userid = $user->id;
1920         insert_record('user_preferences',$pref, false);
1921     }
1924 /**
1925 * @param $user - object containing an id
1926  * @param $reset - will reset the count to 0
1927  */
1928 function set_bounce_count($user,$reset=false) {
1929     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1930         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1931         update_record('user_preferences',$pref);
1932     }
1933     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1934         // make a new one
1935         $pref->name = 'email_bounce_count';
1936         $pref->value = 1;
1937         $pref->userid = $user->id;
1938         insert_record('user_preferences',$pref, false);
1939     }
1942 /**
1943  * Keeps track of login attempts
1944  *
1945  * @uses $SESSION
1946  */
1947 function update_login_count() {
1949     global $SESSION;
1951     $max_logins = 10;
1953     if (empty($SESSION->logincount)) {
1954         $SESSION->logincount = 1;
1955     } else {
1956         $SESSION->logincount++;
1957     }
1959     if ($SESSION->logincount > $max_logins) {
1960         unset($SESSION->wantsurl);
1961         error(get_string('errortoomanylogins'));
1962     }
1965 /**
1966  * Resets login attempts
1967  *
1968  * @uses $SESSION
1969  */
1970 function reset_login_count() {
1971     global $SESSION;
1973     $SESSION->logincount = 0;
1976 function sync_metacourses() {
1978     global $CFG;
1980     if (!$courses = get_records('course', 'metacourse', 1)) {
1981         return;
1982     }
1984     foreach ($courses as $course) {
1985         sync_metacourse($course);
1986     }
1989 /**
1990  * Goes through all enrolment records for the courses inside the metacourse and sync with them.
1991  * 
1992  * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
1993  */
1994 function sync_metacourse($course) {
1995     global $CFG;
1997     // Check the course is valid.
1998     if (!is_object($course)) {
1999         if (!$course = get_record('course', 'id', $course)) {
2000             return false; // invalid course id
2001         }
2002     }
2003     
2004     // Check that we actually have a metacourse.
2005     if (empty($course->metacourse)) {
2006         return false;
2007     }
2009     // Get a list of roles that should not be synced.
2010     if ($CFG->nonmetacoursesyncroleids) {
2011         $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2012     } else { 
2013         $roleexclusions = '';
2014     }
2016     // Get the context of the metacourse.
2017     $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2019     // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2020     if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2021         $managers = array_keys($users);
2022     } else {
2023         $managers = array();
2024     }
2026     // Get assignments of a user to a role that exist in a child course, but
2027     // not in the meta coure. That is, get a list of the assignments that need to be made.
2028     if (!$assignments = get_records_sql("
2029             SELECT
2030                 ra.id, ra.roleid, ra.userid
2031             FROM
2032                 {$CFG->prefix}role_assignments ra,
2033                 {$CFG->prefix}context con,
2034                 {$CFG->prefix}course_meta cm
2035             WHERE
2036                 ra.contextid = con.id AND
2037                 con.contextlevel = " . CONTEXT_COURSE . " AND
2038                 con.instanceid = cm.child_course AND
2039                 cm.parent_course = {$course->id} AND
2040                 $roleexclusions
2041                 NOT EXISTS (
2042                     SELECT 1 FROM
2043                         {$CFG->prefix}role_assignments ra2
2044                     WHERE
2045                         ra2.userid = ra.userid AND
2046                         ra2.roleid = ra.roleid AND
2047                         ra2.contextid = {$context->id}
2048                 )
2049     ")) {
2050         $assignments = array();
2051     }
2053     // Get assignments of a user to a role that exist in the meta course, but
2054     // not in any child courses. That is, get a list of the unassignments that need to be made.
2055     if (!$unassignments = get_records_sql("
2056             SELECT
2057                 ra.id, ra.roleid, ra.userid
2058             FROM
2059                 {$CFG->prefix}role_assignments ra
2060             WHERE
2061                 ra.contextid = {$context->id} AND
2062                 $roleexclusions
2063                 NOT EXISTS (
2064                     SELECT 1 FROM
2065                         {$CFG->prefix}role_assignments ra2,
2066                         {$CFG->prefix}context con2,
2067                         {$CFG->prefix}course_meta cm
2068                     WHERE
2069                         ra2.userid = ra.userid AND
2070                         ra2.roleid = ra.roleid AND
2071                         ra2.contextid = con2.id AND
2072                         con2.contextlevel = " . CONTEXT_COURSE . " AND
2073                         con2.instanceid = cm.child_course AND
2074                         cm.parent_course = {$course->id}
2075                 )
2076     ")) {
2077         $unassignments = array();
2078     }
2080     $success = true;
2082     // Make the unassignments, if they are not managers.
2083     foreach ($unassignments as $unassignment) {
2084         if (!in_array($unassignment->userid, $managers)) {
2085             $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2086         }
2087     }
2089     // Make the assignments.
2090     foreach ($assignments as $assignment) {
2091         $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id) && $success;
2092     }
2094     return $success;
2095     
2096 // TODO: finish timeend and timestart
2097 // maybe we could rely on cron job to do the cleaning from time to time
2100 /**
2101  * Adds a record to the metacourse table and calls sync_metacoures
2102  */
2103 function add_to_metacourse ($metacourseid, $courseid) {
2105     if (!$metacourse = get_record("course","id",$metacourseid)) {
2106         return false;
2107     }
2109     if (!$course = get_record("course","id",$courseid)) {
2110         return false;
2111     }
2113     if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2114         $rec = new object();
2115         $rec->parent_course = $metacourseid;
2116         $rec->child_course = $courseid;
2117         if (!insert_record('course_meta',$rec)) {
2118             return false;
2119         }
2120         return sync_metacourse($metacourseid);
2121     }
2122     return true;
2126 /**
2127  * Removes the record from the metacourse table and calls sync_metacourse
2128  */
2129 function remove_from_metacourse($metacourseid, $courseid) {
2131     if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2132         return sync_metacourse($metacourseid);
2133     }
2134     return false;
2138 /**
2139  * Determines if a user is currently logged in
2140  *
2141  * @uses $USER
2142  * @return bool
2143  */
2144 function isloggedin() {
2145     global $USER;
2147     return (!empty($USER->id));
2150 /**
2151  * Determines if a user is logged in as real guest user with username 'guest'.
2152  * This function is similar to original isguest() in 1.6 and earlier.
2153  * Current isguest() is deprecated - do not use it anymore.
2154  *
2155  * @param $user mixed user object or id, $USER if not specified
2156  * @return bool true if user is the real guest user, false if not logged in or other user
2157  */
2158 function isguestuser($user=NULL) {
2159     global $USER;
2160     if ($user === NULL) {
2161         $user = $USER;
2162     } else if (is_numeric($user)) {
2163         $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2164     }
2166     if (empty($user->id)) {
2167         return false; // not logged in, can not be guest
2168     }
2170     return ($user->username == 'guest');
2173 /**
2174  * Determines if the currently logged in user is in editing mode
2175  *
2176  * @uses $USER
2177  * @param int $courseid The id of the course being tested
2178  * @param user $user A {@link $USER} object. If null then the currently logged in user is used.
2179  * @return bool
2180  */
2181 function isediting($courseid, $user=NULL) {
2182     global $USER;
2183     if (!$user) {
2184         $user = $USER;
2185     }
2186     if (empty($user->editing)) {
2187         return false;
2188     }
2189     return ($user->editing and has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $courseid)));
2192 /**
2193  * Determines if the logged in user is currently moving an activity
2194  *
2195  * @uses $USER
2196  * @param int $courseid The id of the course being tested
2197  * @return bool
2198  */
2199 function ismoving($courseid) {
2200     global $USER;
2202     if (!empty($USER->activitycopy)) {
2203         return ($USER->activitycopycourse == $courseid);
2204     }
2205     return false;
2208 /**
2209  * Given an object containing firstname and lastname
2210  * values, this function returns a string with the
2211  * full name of the person.
2212  * The result may depend on system settings
2213  * or language.  'override' will force both names
2214  * to be used even if system settings specify one.
2215  *
2216  * @uses $CFG
2217  * @uses $SESSION
2218  * @param object $user A {@link $USER} object to get full name of
2219  * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2220  */
2221 function fullname($user, $override=false) {
2223     global $CFG, $SESSION;
2225     if (!isset($user->firstname) and !isset($user->lastname)) {
2226         return '';
2227     }
2229     if (!$override) {
2230         if (!empty($CFG->forcefirstname)) {
2231             $user->firstname = $CFG->forcefirstname;
2232         }
2233         if (!empty($CFG->forcelastname)) {
2234             $user->lastname = $CFG->forcelastname;
2235         }
2236     }
2238     if (!empty($SESSION->fullnamedisplay)) {
2239         $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2240     }
2242     if ($CFG->fullnamedisplay == 'firstname lastname') {
2243         return $user->firstname .' '. $user->lastname;
2245     } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2246         return $user->lastname .' '. $user->firstname;
2248     } else if ($CFG->fullnamedisplay == 'firstname') {
2249         if ($override) {
2250             return get_string('fullnamedisplay', '', $user);
2251         } else {
2252             return $user->firstname;
2253         }
2254     }
2256     return get_string('fullnamedisplay', '', $user);
2259 /**
2260  * Sets a moodle cookie with an encrypted string
2261  *
2262  * @uses $CFG
2263  * @uses DAYSECS
2264  * @uses HOURSECS
2265  * @param string $thing The string to encrypt and place in a cookie
2266  */
2267 function set_moodle_cookie($thing) {
2268     global $CFG;
2270     if ($thing == 'guest') {  // Ignore guest account
2271         return;
2272     }
2274     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2276     $days = 60;
2277     $seconds = DAYSECS*$days;
2279     setCookie($cookiename, '', time() - HOURSECS, '/');
2280     setCookie($cookiename, rc4encrypt($thing), time()+$seconds, '/');
2283 /**
2284  * Gets a moodle cookie with an encrypted string
2285  *
2286  * @uses $CFG
2287  * @return string
2288  */
2289 function get_moodle_cookie() {
2290     global $CFG;
2292     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2294     if (empty($_COOKIE[$cookiename])) {
2295         return '';
2296     } else {
2297         $thing = rc4decrypt($_COOKIE[$cookiename]);
2298         return ($thing == 'guest') ? '': $thing;  // Ignore guest account
2299     }
2302 /**
2303  * Returns whether a given authentication plugin exists.
2304  *
2305  * @uses $CFG
2306  * @param string $auth Form of authentication to check for. Defaults to the
2307  *        global setting in {@link $CFG}.
2308  * @return boolean Whether the plugin is available.
2309  */
2310 function exists_auth_plugin($auth='') {
2311     global $CFG;
2312     
2313     // use the global default if not specified
2314     if ($auth == '') {
2315         $auth = $CFG->auth;
2316     }
2317     if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2318         return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2319     }
2320     return false;
2323 /**
2324  * Checks if a given plugin is in the list of enabled authentication plugins.
2325  * 
2326  * @param string $auth Authentication plugin.
2327  * @return boolean Whether the plugin is enabled.
2328  */
2329 function is_enabled_auth($auth='') {
2330     global $CFG;
2332     // use the global default if not specified
2333     if ($auth == '') {
2334         $auth = $CFG->auth;
2335     }
2336     return in_array($auth, explode(',', $CFG->auth_plugins_enabled));
2339 /**
2340  * Returns an authentication plugin instance.
2341  *
2342  * @uses $CFG
2343  * @param string $auth Form of authentication required. Defaults to the
2344  *        global setting in {@link $CFG}.
2345  * @return object An instance of the required authentication plugin.
2346  */
2347 function get_auth_plugin($auth = '') {
2348     global $CFG;
2349     
2350     // use the global default if not specified
2351     if ($auth == '') {
2352         $auth = $CFG->auth;
2353     }
2355     // TODO: plugin enabled?
2356     
2357     // check the plugin exists first
2358     if (! exists_auth_plugin($auth)) {
2359         error("Authentication plugin '$auth' not found.");
2360     }
2361     
2362     // return auth plugin instance
2363     require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2364     $class = "auth_plugin_$auth";
2365     return new $class;
2368 /**
2369  * Returns true if an internal authentication method is being used.
2370  * if method not specified then, global default is assumed
2371  *
2372  * @uses $CFG
2373  * @param string $auth Form of authentication required
2374  * @return bool
2375  * @todo Outline auth types and provide code example
2376  */
2377 function is_internal_auth($auth='') {
2378     $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2379     return $authplugin->is_internal();
2382 /**
2383  * Returns an array of user fields
2384  *
2385  * @uses $CFG
2386  * @uses $db
2387  * @return array User field/column names
2388  */
2389 function get_user_fieldnames() {
2391     global $CFG, $db;
2393     $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2394     unset($fieldarray['ID']);
2396     return $fieldarray;
2399 /**
2400  * Creates a bare-bones user record
2401  *
2402  * @uses $CFG
2403  * @param string $username New user's username to add to record
2404  * @param string $password New user's password to add to record
2405  * @param string $auth Form of authentication required
2406  * @return object A {@link $USER} object
2407  * @todo Outline auth types and provide code example
2408  */
2409 function create_user_record($username, $password, $auth='') {
2410     global $CFG;
2412     //just in case check text case
2413     $username = trim(moodle_strtolower($username));
2415     $authplugin = get_auth_plugin($auth);
2417     if (method_exists($authplugin, 'get_userinfo')) {
2418         if ($newinfo = $authplugin->get_userinfo($username)) {
2419             $newinfo = truncate_userinfo($newinfo);
2420             foreach ($newinfo as $key => $value){
2421                 $newuser->$key = addslashes(stripslashes($value)); // Just in case
2422             }
2423         }
2424     }
2426     if (!empty($newuser->email)) {
2427         if (email_is_not_allowed($newuser->email)) {
2428             unset($newuser->email);
2429         }
2430     }
2432     $newuser->auth = (empty($auth)) ? $CFG->auth : $auth;
2433     $newuser->username = $username;
2434     update_internal_user_password($newuser, $password, false);
2435     $newuser->lang = $CFG->lang;
2436     $newuser->confirmed = 1;
2437     $newuser->lastip = getremoteaddr();
2438     $newuser->timemodified = time();
2439     $newuser->mnethostid = $CFG->mnet_localhost_id;
2441     if (insert_record('user', $newuser)) {
2442          $user = get_complete_user_data('username', $newuser->username);
2443          if($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'}){
2444              set_user_preference('auth_forcepasswordchange', 1, $user->id);
2445          }
2446          return $user;
2447     }
2448     return false;
2451 /**
2452  * Will update a local user record from an external source
2453  *
2454  * @uses $CFG
2455  * @param string $username New user's username to add to record
2456  * @return user A {@link $USER} object
2457  */
2458 function update_user_record($username, $authplugin) {
2459     if (method_exists($authplugin, 'get_userinfo')) {
2460         $username = trim(moodle_strtolower($username)); /// just in case check text case
2462         $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2463         $userauth = get_auth_plugin($oldinfo->auth);
2465         if ($newinfo = $authplugin->get_userinfo($username)) {
2466             $newinfo = truncate_userinfo($newinfo);
2467             foreach ($newinfo as $key => $value){
2468                 $confkey = 'field_updatelocal_' . $key;
2469                 if (!empty($userauth->config->$confkey) and $userauth->config->$confkey === 'onlogin') {
2470                     $value = addslashes(stripslashes($value));   // Just in case
2471                     set_field('user', $key, $value, 'username', $username)
2472                         or error_log("Error updating $key for $username");
2473                 }
2474             }
2475         }
2476     }
2477     return get_complete_user_data('username', $username);
2480 function truncate_userinfo($info) {
2481 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2482 /// which may have large fields
2484     // define the limits
2485     $limit = array(
2486                     'username'    => 100,
2487                     'idnumber'    =>  64,
2488                     'firstname'   => 100,
2489                     'lastname'    => 100,
2490                     'email'       => 100,
2491                     'icq'         =>  15,
2492                     'phone1'      =>  20,
2493                     'phone2'      =>  20,
2494                     'institution' =>  40,
2495                     'department'  =>  30,
2496                     'address'     =>  70,
2497                     'city'        =>  20,
2498                     'country'     =>   2,
2499                     'url'         => 255,
2500                     );
2502     // apply where needed
2503     foreach (array_keys($info) as $key) {
2504         if (!empty($limit[$key])) {
2505             $info[$key] = trim(substr($info[$key],0, $limit[$key]));
2506         }
2507     }
2509     return $info;
2512 /**
2513  * Retrieve the guest user object
2514  *
2515  * @uses $CFG
2516  * @return user A {@link $USER} object
2517  */
2518 function guest_user() {
2519     global $CFG;
2521     if ($newuser = get_record('user', 'username', 'guest')) {
2522         $newuser->confirmed = 1;
2523         $newuser->lang = $CFG->lang;
2524         $newuser->lastip = getremoteaddr();
2525     }
2527     return $newuser;
2530 /**
2531  * Given a username and password, this function looks them
2532  * up using the currently selected authentication mechanism,
2533  * and if the authentication is successful, it returns a
2534  * valid $user object from the 'user' table.
2535  *
2536  * Uses auth_ functions from the currently active auth module
2537  *
2538  * @uses $CFG
2539  * @param string $username  User's username
2540  * @param string $password  User's password
2541  * @return user|flase A {@link $USER} object or false if error
2542  */
2543 function authenticate_user_login($username, $password) {
2545     global $CFG;
2547     // default to manual if global auth is undefined or broken
2548     if (empty($CFG->auth_plugins_enabled)) {
2549         $CFG->auth_plugins_enabled = empty($CFG->auth) ? 'manual' : $CFG->auth;
2550     }
2551     // if blank, set default auth to first enabled auth plugin
2552     if (empty($CFG->auth)) {
2553         $auths = explode(',', $CFG->auth_plugins_enabled);
2554         $CFG->auth = $auths[0];
2555     }
2557     // if user not found, use site auth
2558     if (!$user = get_complete_user_data('username', $username)) {
2559         $user = new object();
2560         $user->id = 0;     // Not a user
2561         $auth = $CFG->auth_plugins_enabled;
2562     }
2564     // Sort out the authentication method we are using.
2565     if (empty($user->auth)) {      // For some reason it isn't set yet
2566         $primadmin = get_admin();
2567         if (!empty($user->id) && (($user->id==$primadmin->id) || isguest($user->id))) {
2568             $auth = 'manual';    // always assume these guys are internal
2569         }
2570         else {
2571             $auth = $CFG->auth_plugins_enabled; // default to site method
2572         }
2573     } else {
2574         $auth = $user->auth;
2575     }
2577     // walk each authentication plugin, in order
2578     $auths = explode(',', $auth);
2579     foreach ($auths as $auth) {
2580         $authplugin = get_auth_plugin($auth);
2582         // on auth fail, log and fall through to the next plugin
2583         if (!$authplugin->user_login($username, $password)) {
2584             add_to_log(0, 'login', 'error', 'index.php', $username);
2585             error_log("[client {$_SERVER['REMOTE_ADDR']}]  $CFG->wwwroot  Auth=$auth  Failed Login:  $username  {$_SERVER['HTTP_USER_AGENT']}");
2586             continue;
2587         }
2589         // successful authentication
2590         if ($user->id) {                          // User already exists in database
2591             if (empty($user->auth)) {             // For some reason auth isn't set yet
2592                 set_field('user', 'auth', $auth, 'username', $username);
2593             }
2594             update_internal_user_password($user, $password);
2595             if (!$authplugin->is_internal()) {            // update user record from external DB
2596                 $user = update_user_record($username, get_auth_plugin($user->auth));
2597             }
2598         } else {
2599             $user = create_user_record($username, $password, $auth);
2600         }
2601         // fix for MDL-6928
2602         if (method_exists($authplugin, 'iscreator')) {
2603             $sitecontext = get_context_instance(CONTEXT_SYSTEM);
2604             if ($creatorroles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
2605                 $creatorrole = array_shift($creatorroles); // We can only use one, let's use the first one
2606                 // Check if the user is a creator
2607                 if ($authplugin->iscreator($username)) { // Following calls will not create duplicates
2608                     role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, $auth);
2609                 } else {
2610                     role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id);
2611                 }
2612             }
2613         }
2615     /// Log in to a second system if necessary
2616         if (!empty($CFG->sso)) {
2617             include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
2618             if (function_exists('sso_user_login')) {
2619                 if (!sso_user_login($username, $password)) {   // Perform the signon process
2620                     notify('Second sign-on failed');
2621                 }
2622             }
2623         }
2625         return $user;
2627     } 
2628     
2629     // failed if all the plugins have failed
2630     add_to_log(0, 'login', 'error', 'index.php', $username);
2631     error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Failed Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
2632     return false;
2635 /**
2636  * Compare password against hash stored in internal user table.
2637  * If necessary it also updates the stored hash to new format.
2638  * 
2639  * @param object user
2640  * @param string plain text password
2641  * @return bool is password valid?
2642  */
2643 function validate_internal_user_password(&$user, $password) {
2644     global $CFG;
2646     if (!isset($CFG->passwordsaltmain)) {
2647         $CFG->passwordsaltmain = '';
2648     }
2650     $validated = false;
2652         // get password original encoding in case it was not updated to unicode yet
2653     $textlib = textlib_get_instance();
2654     $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
2656     if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
2657         or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
2658         $validated = true;
2659     } else {
2660         for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
2661             $alt = 'passwordsaltalt'.$i;
2662             if (!empty($CFG->$alt)) {
2663                 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
2664                     $validated = true;
2665                     break;
2666                 }
2667             }
2668         }
2669     }
2671     if ($validated) {
2672         // force update of password hash using latest main password salt and encoding if needed
2673         update_internal_user_password($user, $password);
2674     }
2676     return $validated;
2679 /**
2680  * Calculate hashed value from password using current hash mechanism.
2681  * 
2682  * @param string password
2683  * @return string password hash
2684  */
2685 function hash_internal_user_password($password) {
2686     global $CFG;
2688     if (isset($CFG->passwordsaltmain)) {
2689         return md5($password.$CFG->passwordsaltmain);
2690     } else {
2691         return md5($password);
2692     }
2695 /**
2696  * Update pssword hash in user object.
2697  * 
2698  * @param object user
2699  * @param string plain text password
2700  * @param bool store changes also in db, default true
2701  * @return true if hash changed
2702  */
2703 function update_internal_user_password(&$user, $password, $storeindb=true) {
2704     global $CFG;
2706     $authplugin = get_auth_plugin($user->auth);
2707     if (!empty($authplugin->config->preventpassindb) /*|| $storeindb === false */) {
2708         $hashedpassword = 'not cached';
2709     } else {
2710         $hashedpassword = hash_internal_user_password($password);
2711     }
2713     return set_field('user', 'password',  $hashedpassword, 'id', $user->id);
2716 /**
2717  * Get a complete user record, which includes all the info
2718  * in the user record
2719  * Intended for setting as $USER session variable
2720  *
2721  * @uses $CFG
2722  * @uses SITEID
2723  * @param string $field The user field to be checked for a given value.
2724  * @param string $value The value to match for $field.
2725  * @return user A {@link $USER} object.
2726  */
2727 function get_complete_user_data($field, $value, $mnethostid=null) {
2729     global $CFG;
2731     if (!$field || !$value) {
2732         return false;
2733     }
2735 /// Build the WHERE clause for an SQL query
2737     $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
2739     if (null === $mnethostid) {
2740         $constraints .= ' AND auth != \'mnet\'';
2741     } elseif (is_numeric($mnethostid)) {
2742         $constraints .= ' AND mnethostid = \''.$mnethostid.'\'';
2743     } else {
2744         error_log('Call to get_complete_user_data for $field='.$field.', $value = '.$value.', with invalid $mnethostid: '. $mnethostid);
2745         print_error('invalidhostlogin','mnet', $CFG->wwwroot.'/login/index.php');
2746         exit;
2747     }
2749 /// Get all the basic user data
2751     if (! $user = get_record_select('user', $constraints)) {
2752         return false;
2753     }
2755 /// Get various settings and preferences
2757     if ($displays = get_records('course_display', 'userid', $user->id)) {
2758         foreach ($displays as $display) {
2759             $user->display[$display->course] = $display->display;
2760         }
2761     }
2763     if ($preferences = get_records('user_preferences', 'userid', $user->id)) {
2764         foreach ($preferences as $preference) {
2765             $user->preference[$preference->name] = $preference->value;
2766         }
2767     }
2769     if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
2770         foreach ($lastaccesses as $lastaccess) {
2771             $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
2772         }
2773     }
2775     if ($groupids = groups_get_all_groups_for_user($user->id)) { //TODO:check.
2776         foreach ($groupids as $groupid) {
2777             $courseid = groups_get_course($groupid);
2778             //change this to 2D array so we can put multiple groups in a course
2779             $user->groupmember[$courseid][] = $groupid;
2780         }
2781     }
2783 /// Rewrite some variables if necessary
2784     if (!empty($user->description)) {
2785         $user->description = true;   // No need to cart all of it around
2786     }
2787     if ($user->username == 'guest') {
2788         $user->lang       = $CFG->lang;               // Guest language always same as site
2789         $user->firstname  = get_string('guestuser');  // Name always in current language
2790         $user->lastname   = ' ';
2791     }
2793     $user->sesskey  = random_string(10);
2794     $user->sessionIP = md5(getremoteaddr());   // Store the current IP in the session
2796     return $user;
2801 /*
2802  * When logging in, this function is run to set certain preferences
2803  * for the current SESSION
2804  */
2805 function set_login_session_preferences() {
2806     global $SESSION, $CFG;
2808     $SESSION->justloggedin = true;
2810     unset($SESSION->lang);
2812     // Restore the calendar filters, if saved
2813     if (intval(get_user_preferences('calendar_persistflt', 0))) {
2814         include_once($CFG->dirroot.'/calendar/lib.php');
2815         calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
2816     }
2820 /**
2821  * Delete a course, including all related data from the database,
2822  * and any associated files from the moodledata folder.
2823  *
2824  * @param int $courseid The id of the course to delete.
2825  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2826  * @return bool true if all the removals succeeded. false if there were any failures. If this
2827  *             method returns false, some of the removals will probably have succeeded, and others
2828  *             failed, but you have no way of knowing which.
2829  */
2830 function delete_course($courseid, $showfeedback = true) {
2831     global $CFG;
2832     $result = true;
2834     if (!remove_course_contents($courseid, $showfeedback)) {
2835         if ($showfeedback) {
2836             notify("An error occurred while deleting some of the course contents.");
2837         }
2838         $result = false;
2839     }
2841     if (!delete_records("course", "id", $courseid)) {
2842         if ($showfeedback) {
2843             notify("An error occurred while deleting the main course record.");
2844         }
2845         $result = false;
2846     }
2848     if (!delete_records('context', 'contextlevel', CONTEXT_COURSE, 'instanceid', $courseid)) {
2849         if ($showfeedback) {
2850             notify("An error occurred while deleting the main context record.");
2851         }
2852         $result = false;
2853     }
2855     if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
2856         if ($showfeedback) {
2857             notify("An error occurred while deleting the course files.");
2858         }
2859         $result = false;
2860     }
2862     return $result;
2865 /**
2866  * Clear a course out completely, deleting all content
2867  * but don't delete the course itself
2868  *
2869  * @uses $CFG
2870  * @param int $courseid The id of the course that is being deleted
2871  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2872  * @return bool true if all the removals succeeded. false if there were any failures. If this
2873  *             method returns false, some of the removals will probably have succeeded, and others
2874  *             failed, but you have no way of knowing which.
2875  */
2876 function remove_course_contents($courseid, $showfeedback=true) {
2878     global $CFG;
2880     $result = true;
2882     if (! $course = get_record('course', 'id', $courseid)) {
2883         error('Course ID was incorrect (can\'t find it)');
2884     }
2886     $strdeleted = get_string('deleted');
2888 /// First delete every instance of every module
2890     if ($allmods = get_records('modules') ) {
2891         foreach ($allmods as $mod) {
2892             $modname = $mod->name;
2893             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
2894             $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance
2895             $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon)
2896             $count=0;
2897             if (file_exists($modfile)) {
2898                 include_once($modfile);
2899                 if (function_exists($moddelete)) {
2900                     if ($instances = get_records($modname, 'course', $course->id)) {
2901                         foreach ($instances as $instance) {
2902                             if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
2903                                 delete_context(CONTEXT_MODULE, $cm->id);
2904                             }
2905                             if ($moddelete($instance->id)) {
2906                                 $count++;
2908                             } else {
2909                                 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
2910                                 $result = false;
2911                             }
2912                         }
2913                     }
2914                 } else {
2915                     notify('Function '. $moddelete() .'doesn\'t exist!');
2916                     $result = false;
2917                 }
2919                 if (function_exists($moddeletecourse)) {
2920                     $moddeletecourse($course, $showfeedback);
2921                 }
2922             }
2923             if ($showfeedback) {
2924                 notify($strdeleted .' '. $count .' x '. $modname);
2925             }
2926         }
2927     } else {
2928         error('No modules are installed!');
2929     }
2931 /// Give local code a chance to delete its references to this course.
2932     require_once('locallib.php');
2933     notify_local_delete_course($courseid, $showfeedback);
2935 /// Delete course blocks
2937     if ($blocks = get_records_sql("SELECT * 
2938                                    FROM {$CFG->prefix}block_instance
2939                                    WHERE pagetype = '".PAGE_COURSE_VIEW."'
2940                                    AND pageid = $course->id")) {
2941         if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
2942             if ($showfeedback) {
2943                 notify($strdeleted .' block_instance');
2944             }
2945             foreach ($blocks as $block) {  /// Delete any associated contexts for this block
2946                 delete_context(CONTEXT_BLOCK, $block->id);
2947             }
2948         } else {
2949             $result = false;
2950         }
2951     }
2953 /// Delete any groups, removing members and grouping/course links first.
2954     //TODO: If groups or groupings are to be shared between courses, think again!
2955     if ($groupids = groups_get_groups($course->id)) {
2956         foreach ($groupids as $groupid) {
2957             if (groups_remove_all_members($groupid)) {
2958                 if ($showfeedback) {
2959                     notify($strdeleted .' groups_members');
2960                 }
2961             } else {
2962                 $result = false;
2963             }
2964             /// Delete any associated context for this group ??
2965             delete_context(CONTEXT_GROUP, $groupid);
2966             
2967             if (groups_delete_group($groupid)) {
2968                 if ($showfeedback) {
2969                     notify($strdeleted .' groups');
2970                 }
2971             } else {
2972                 $result = false;
2973             }
2974         }
2975     }
2976 /// Delete any groupings.
2977     $result = groups_delete_all_groupings($course->id);
2978     if ($result && $showfeedback) {
2979         notify($strdeleted .' groupings');
2980     }
2982 /// Delete all related records in other tables that may have a courseid
2983 /// This array stores the tables that need to be cleared, as
2984 /// table_name => column_name that contains the course id.
2986     $tablestoclear = array(
2987         'event' => 'courseid', // Delete events
2988         'log' => 'course', // Delete logs
2989         'course_sections' => 'course', // Delete any course stuff
2990         'course_modules' => 'course',
2991         'grade_category' => 'courseid', // Delete gradebook stuff
2992         'grade_exceptions' => 'courseid',
2993         'grade_item' => 'courseid',
2994         'grade_letter' => 'courseid',
2995         'grade_preferences' => 'courseid'
2996     );
2997     foreach ($tablestoclear as $table => $col) {
2998         if (delete_records($table, $col, $course->id)) {
2999             if ($showfeedback) {
3000                 notify($strdeleted . ' ' . $table);
3001             }
3002         } else {
3003             $result = false;
3004         }
3005     }
3008 /// Clean up metacourse stuff
3010     if ($course->metacourse) {
3011         delete_records("course_meta","parent_course",$course->id);
3012         sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3013         if ($showfeedback) {
3014             notify("$strdeleted course_meta");
3015         }
3016     } else {
3017         if ($parents = get_records("course_meta","child_course",$course->id)) {
3018             foreach ($parents as $parent) {
3019                 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3020             }
3021             if ($showfeedback) {
3022                 notify("$strdeleted course_meta");
3023             }
3024         }
3025     }
3027 /// Delete questions and question categories
3028     include_once($CFG->libdir.'/questionlib.php');
3029     question_delete_course($course, $showfeedback);
3031 /// Delete all roles and overiddes in the course context (but keep the course context)
3032     if ($courseid != SITEID) {
3033         delete_context(CONTEXT_COURSE, $course->id);
3034     }
3035     
3036     return $result;
3040 /**
3041  * This function will empty a course of USER data as much as
3042 /// possible. It will retain the activities and the structure
3043 /// of the course.
3044  *
3045  * @uses $USER
3046  * @uses $SESSION
3047  * @uses $CFG
3048  * @param object $data an object containing all the boolean settings and courseid
3049  * @param bool $showfeedback  if false then do it all silently
3050  * @return bool
3051  * @todo Finish documenting this function
3052  */
3053 function reset_course_userdata($data, $showfeedback=true) {
3055     global $CFG, $USER, $SESSION;
3057     $result = true;
3059     $strdeleted = get_string('deleted');
3061     // Look in every instance of every module for data to delete
3063     if ($allmods = get_records('modules') ) {
3064         foreach ($allmods as $mod) {
3065             $modname = $mod->name;
3066             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3067             $moddeleteuserdata = $modname .'_delete_userdata';   // Function to delete user data
3068             if (file_exists($modfile)) {
3069                 @include_once($modfile);
3070                 if (function_exists($moddeleteuserdata)) {
3071                     $moddeleteuserdata($data, $showfeedback);
3072                 }
3073             }
3074         }
3075     } else {
3076         error('No modules are installed!');
3077     }
3079     // Delete other stuff
3080     $coursecontext = get_context_instance(CONTEXT_COURSE, $data->courseid);
3082     if (!empty($data->reset_students) or !empty($data->reset_teachers)) {
3083         $teachers     = array_keys(get_users_by_capability($coursecontext, 'moodle/course:update'));
3084         $participants = array_keys(get_users_by_capability($coursecontext, 'moodle/course:view'));
3085         $students     = array_diff($participants, $teachers);
3087         if (!empty($data->reset_students)) {
3088             foreach ($students as $studentid) {
3089                 role_unassign(0, $studentid, 0, $coursecontext->id);
3090             }
3091             if ($showfeedback) {
3092                 notify($strdeleted .' '.get_string('students'), 'notifysuccess');
3093             }
3095             /// Delete group members (but keep the groups) TODO:check.
3096             if ($groupids = groups_get_groups($data->courseid)) {
3097                 foreach ($groupids as $groupid) {
3098                     if (groups_remove_all_group_members($groupid)) {
3099                         if ($showfeedback) {
3100                             notify($strdeleted .' groups_members', 'notifysuccess');
3101                         }
3102                     } else {
3103                         $result = false;
3104                     }
3105                 }
3106             }
3107         }
3109         if (!empty($data->reset_teachers)) {
3110             foreach ($teachers as $teacherid) {
3111                 role_unassign(0, $teacherid, 0, $coursecontext->id);
3112             }
3113             if ($showfeedback) {
3114                 notify($strdeleted .' '.get_string('teachers'), 'notifysuccess');
3115             }
3116         }
3117     }
3119     if (!empty($data->reset_groups)) {
3120         if ($groupids = groups_get_groups($data->courseid)) {
3121             foreach ($groupids as $groupid) {
3122                 if (groups_delete_group($groupid)) {
3123                     if ($showfeedback) {
3124                         notify($strdeleted .' groups', 'notifysuccess');
3125                     }
3126                 } else {
3127                     $result = false;
3128                 }
3129             }
3130         }
3131     }
3133     if (!empty($data->reset_events)) {
3134         if (delete_records('event', 'courseid', $data->courseid)) {
3135             if ($showfeedback) {
3136                 notify($strdeleted .' event', 'notifysuccess');
3137             }
3138         } else {
3139             $result = false;
3140         }
3141     }
3143     if (!empty($data->reset_logs)) {
3144         if (delete_records('log', 'course', $data->courseid)) {
3145             if ($showfeedback) {
3146                 notify($strdeleted .' log', 'notifysuccess');
3147             }
3148         } else {
3149             $result = false;
3150         }
3151     }
3153     // deletes all role assignments, and local override, these have no courseid in table and needs separate process
3154     $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3155     delete_records('role_capabilities', 'contextid', $context->id);
3157     return $result;
3161 require_once($CFG->dirroot.'/group/lib.php');
3162 /*TODO: functions moved to /group/lib/legacylib.php
3164 ismember
3165 add_user_to_group
3166 mygroupid
3167 groupmode
3168 set_current_group
3169 ... */
3172 function generate_email_processing_address($modid,$modargs) {
3173     global $CFG;
3175     if (empty($CFG->siteidentifier)) {    // Unique site identification code
3176         set_config('siteidentifier', random_string(32));
3177     }
3179     $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3180     return $header . substr(md5($header.$CFG->siteidentifier),0,16).'@'.$CFG->maildomain;
3184 function moodle_process_email($modargs,$body) {
3185     // the first char should be an unencoded letter. We'll take this as an action
3186     switch ($modargs{0}) {
3187         case 'B': { // bounce
3188             list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3189             if ($user = get_record_select("user","id=$userid","id,email")) {
3190                 // check the half md5 of their email
3191                 $md5check = substr(md5($user->email),0,16);
3192                 if ($md5check == substr($modargs, -16)) {
3193                     set_bounce_count($user);
3194                 }
3195                 // else maybe they've already changed it?
3196             }
3197         }
3198         break;
3199         // maybe more later?
3200     }
3203 /// CORRESPONDENCE  ////////////////////////////////////////////////
3205 /**
3206  * Send an email to a specified user
3207  *
3208  * @uses $CFG
3209  * @uses $FULLME
3210  * @uses SITEID
3211  * @param user $user  A {@link $USER} object
3212  * @param user $from A {@link $USER} object
3213  * @param string $subject plain text subject line of the email
3214  * @param string $messagetext plain text version of the message
3215  * @param string $messagehtml complete html version of the message (optional)
3216  * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
3217  * @param string $attachname the name of the file (extension indicates MIME)
3218  * @param bool $usetrueaddress determines whether $from email address should
3219  *          be sent out. Will be overruled by user profile setting for maildisplay
3220  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3221  *          was blocked by user and "false" if there was another sort of error.
3222  */
3223 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='') {
3225     global $CFG, $FULLME;
3227     include_once($CFG->libdir .'/phpmailer/class.phpmailer.php');
3229 /// We are going to use textlib services here
3230     $textlib = textlib_get_instance();
3232     if (empty($user)) {
3233         return false;
3234     }
3236     if (!empty($user->emailstop)) {
3237         return 'emailstop';
3238     }
3240     if (over_bounce_threshold($user)) {
3241         error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
3242         return false;
3243     }
3245     $mail = new phpmailer;
3247     $mail->Version = 'Moodle '. $CFG->version;           // mailer version
3248     $mail->PluginDir = $CFG->libdir .'/phpmailer/';      // plugin directory (eg smtp plugin)
3250     $mail->CharSet = 'utf-8';
3252     if ($CFG->smtphosts == 'qmail') {
3253         $mail->IsQmail();                              // use Qmail system
3255     } else if (empty($CFG->smtphosts)) {
3256         $mail->IsMail();                               // use PHP mail() = sendmail
3258     } else {
3259         $mail->IsSMTP();                               // use SMTP directly
3260         if (debugging()) {
3261             echo '<pre>' . "\n";
3262             $mail->SMTPDebug = true;
3263         }
3264         $mail->Host = $CFG->smtphosts;               // specify main and backup servers
3266         if ($CFG->smtpuser) {                          // Use SMTP authentication
3267             $mail->SMTPAuth = true;
3268             $mail->Username = $CFG->smtpuser;
3269             $mail->Password = $CFG->smtppass;
3270         }
3271     }
3273     $adminuser = get_admin();
3275     // make up an email address for handling bounces
3276     if (!empty($CFG->handlebounces)) {
3277         $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
3278         $mail->Sender = generate_email_processing_address(0,$modargs);
3279     }
3280     else {
3281         $mail->Sender   = $adminuser->email;
3282     }
3284     if (is_string($from)) { // So we can pass whatever we want if there is need
3285         $mail->From     = $CFG->noreplyaddress;
3286         $mail->FromName = $from;
3287     } else if ($usetrueaddress and $from->maildisplay) {
3288         $mail->From     = $from->email;
3289         $mail->FromName = fullname($from);
3290     } else {
3291         $mail->From     = $CFG->noreplyaddress;
3292         $mail->FromName = fullname($from);
3293         if (empty($replyto)) {
3294             $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
3295         }
3296     }
3298     if (!empty($replyto)) {
3299         $mail->AddReplyTo($replyto,$replytoname);
3300     }
3302     $mail->Subject = substr(stripslashes($subject), 0, 900);
3304     $mail->AddAddress($user->email, fullname($user) );
3306     $mail->WordWrap = 79;                               // set word wrap
3308     if (!empty($from->customheaders)) {                 // Add custom headers
3309         if (is_array($from->customheaders)) {
3310             foreach ($from->customheaders as $customheader) {
3311                 $mail->AddCustomHeader($customheader);
3312             }
3313         } else {
3314             $mail->AddCustomHeader($from->customheaders);
3315         }
3316     }
3318     if (!empty($from->priority)) {
3319         $mail->Priority = $from->priority;
3320     }
3322     if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
3323         $mail->IsHTML(true);
3324         $mail->Encoding = 'quoted-printable';           // Encoding to use
3325         $mail->Body    =  $messagehtml;
3326         $mail->AltBody =  "\n$messagetext\n";
3327     } else {
3328         $mail->IsHTML(false);
3329         $mail->Body =  "\n$messagetext\n";
3330     }
3332     if ($attachment && $attachname) {
3333         if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
3334             $mail->AddAddress($adminuser->email, fullname($adminuser) );
3335             $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
3336         } else {
3337             require_once($CFG->libdir.'/filelib.php');
3338             $mimetype = mimeinfo('type', $attachname);
3339             $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
3340         }
3341     }
3345 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
3346 /// encoding to the specified one
3347     if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
3348     /// Set it to site mail charset
3349         $charset = $CFG->sitemailcharset;
3350     /// Overwrite it with the user mail charset
3351         if (!empty($CFG->allowusermailcharset)) {
3352             if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
3353                 $charset = $useremailcharset;
3354             }
3355         }
3356     /// If it has changed, convert all the necessary strings
3357         if ($mail->CharSet != $charset) {
3358         /// Save the new mail charset
3359             $mail->CharSet = $charset;
3360         /// And convert some strings
3361             $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
3362             foreach ($mail->ReplyTo as $key => $rt) {                                      //ReplyTo Names
3363                 $mail->ReplyTo[$key][1] = $textlib->convert($rt, 'utf-8', $mail->CharSet);
3364             }
3365             $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet);   //Subject
3366             foreach ($mail->to as $key => $to) {
3367                 $mail->to[$key][1] = $textlib->convert($to, 'utf-8', $mail->CharSet);      //To Names
3368             }
3369             $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet);         //Body
3370             $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet);   //Subject
3371         }
3372     }
3374     if ($mail->Send()) {
3375         set_send_count($user);
3376         return true;
3377     } else {
3378         mtrace('ERROR: '. $mail->ErrorInfo);
3379         add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
3380         return false;
3381     }
3384 /**
3385  * Sets specified user's password and send the new password to the user via email.
3386  *
3387  * @uses $CFG
3388  * @param user $user A {@link $USER} object
3389  * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
3390  *          was blocked by user and "false" if there was another sort of error.
3391  */
3392 function setnew_password_and_mail($user) {
3394     global $CFG;
3396     $site  = get_site();
3397     $from = get_admin();
3399     $newpassword = generate_password();
3401     if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
3402         trigger_error('Could not set user password!');
3403         return false;
3404     }
3406     $a = new object();
3407     $a->firstname   = $user->firstname;
3408     $a->sitename    = $site->fullname;
3409     $a->username    = $user->username;
3410     $a->newpassword = $newpassword;
3411     $a->link        = $CFG->wwwroot .'/login/';
3412     $a->signoff     = fullname($from, true).' ('. $from->email .')';
3414     $message = get_string('newusernewpasswordtext', '', $a);
3416     $subject  = $site->fullname .': '. get_string('newusernewpasswordsubj');
3418     return email_to_user($user, $from, $subject, $message);
3422 /**
3423  * Resets specified user's password and send the new password to the user via email.
3424  *
3425  * @uses $CFG
3426  * @param user $user A {@link $USER} object
3427  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3428  *          was blocked by user and "false" if there was another sort of error.
3429  */
3430 function reset_password_and_mail($user) {
3432     global $CFG;
3434     $site  = get_site();
3435     $from = get_admin();
3437     $external = false;
3438     
3439     $userauth = get_auth_plugin($user->auth);
3440     if (!$userauth->can_change_password()) {
3441         trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
3442         return false;
3443     }
3445     $newpassword = generate_password();
3447     if (!$userauth->user_update_password($user->username, $newpassword)) {
3448         error("Could not set user password!");
3449     }
3451     $a = new object();
3452     $a->firstname = $user->firstname;
3453     $a->sitename = $site->fullname;
3454     $a->username = $user->username;
3455     $a->newpassword = $newpassword;
3456     $a->link = $CFG->httpswwwroot .'/login/change_password.php';
3457     $a->signoff = fullname($from, true).' ('. $from->email .')';
3459     $message = get_string('newpasswordtext', '', $a);
3461     $subject  = $site->fullname .': '. get_string('changedpassword');
3463     return email_to_user($user, $from, $subject, $message);
3467 /**
3468  * Send email to specified user with confirmation text and activation link.
3469  *
3470  * @uses $CFG
3471  * @param user $user A {@link $USER} object
3472  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3473  *          was blocked by user and "false" if there was another sort of error.
3474  */
3475  function send_confirmation_email($user) {
3477     global $CFG;
3479     $site = get_site();
3480     $from = get_admin();
3482     $data = new object();
3483     $data->firstname = fullname($user);
3484     $data->sitename = $site->fullname;
3485     $data->admin = fullname($from) .' ('. $from->email .')';
3487     $subject = get_string('emailconfirmationsubject', '', $site->fullname);
3489     $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $user->username;
3490     $message     = get_string('emailconfirmation', '', $data);
3491     $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
3493     $user->mailformat = 1;  // Always send HTML version as well
3495     return email_to_user($user, $from, $subject, $message, $messagehtml);
3499 /**
3500  * send_password_change_confirmation_email.
3501  *
3502  * @uses $CFG
3503  * @param user $user A {@link $USER} object
3504  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3505  *          was blocked by user and "false" if there was another sort of error.
3506  */
3507 function send_password_change_confirmation_email($user) {
3509     global $CFG;
3511     $site = get_site();
3512     $from = get_admin();
3514     $data = new object();
3515     $data->firstname = $user->firstname;
3516     $data->sitename = $site->fullname;
3517     $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. $user->username;
3518     $data->admin = fullname($from).' ('. $from->email .')';
3520     $message = get_string('emailpasswordconfirmation', '', $data);
3521     $subject = get_string('emailpasswordconfirmationsubject', '', $site->fullname);
3523     return email_to_user($user, $from, $subject, $message);
3527 /**
3528  * Check that an email is allowed.  It returns an error message if there
3529  * was a problem.
3530  *
3531  * @uses $CFG
3532  * @param  string $email Content of email
3533  * @return string|false
3534  */
3535 function email_is_not_allowed($email) {
3537     global $CFG;
3539     if (!empty($CFG->allowemailaddresses)) {
3540         $allowed = explode(' ', $CFG->allowemailaddresses);
3541         foreach ($allowed as $allowedpattern) {
3542             $allowedpattern = trim($allowedpattern);
3543             if (!$allowedpattern) {
3544                 continue;
3545             }
3546             if (strpos(strrev($email), strrev($allowedpattern)) === 0) { // Match!   (bug 5250)
3547                 return false;
3548             }
3549         }
3550         return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
3552     } else if (!empty($CFG->denyemailaddresses)) {
3553         $denied = explode(' ', $CFG->denyemailaddresses);
3554         foreach ($denied as $deniedpattern) {
3555             $deniedpattern = trim($deniedpattern);
3556             if (!$deniedpattern) {
3557                 continue;
3558             }
3559             if (strpos(strrev($email), strrev($deniedpattern)) === 0) { // Match!   (bug 5250)
3560                 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
3561             }
3562         }
3563     }
3565     return false;
3568 function email_welcome_message_to_user($course, $user=NULL) {
3569     global $CFG, $USER;
3571     if (empty($user)) {
3572         if (!isloggedin()) {
3573             return false;
3574         }
3575         $user = $USER;
3576     }
3578     if (!empty($course->welcomemessage)) {
3579         $subject = get_string('welcometocourse', '', $course->fullname);
3581         $a->coursename = $course->fullname;
3582         $a->profileurl = "$CFG->wwwroot/user/view.php?id=$USER->id&course=$course->id";
3583         //$message = get_string("welcometocoursetext", "", $a);
3584         $message = $course->welcomemessage;
3586         if (! $teacher = get_teacher($course->id)) {
3587             $teacher = get_admin();
3588         }
3589         email_to_user($user, $teacher, $subject, $message);
3590     }
3593 /// FILE HANDLING  /////////////////////////////////////////////
3596 /**
3597  * Makes an upload directory for a particular module.
3598  *
3599  * @uses $CFG
3600  * @param int $courseid The id of the course in question - maps to id field of 'course' table.
3601  * @return string|false Returns full path to directory if successful, false if not
3602  */
3603 function make_mod_upload_directory($courseid) {
3604     global $CFG;
3606     if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
3607         return false;
3608     }
3610     $strreadme = get_string('readme');
3612     if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
3613         copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3614     } else {
3615         copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3616     }
3617     return $moddata;
3620 /**
3621  * Returns current name of file on disk if it exists.
3622  *
3623  * @param string $newfile File to be verified
3624  * @return string Current name of file on disk if true
3625  */
3626 function valid_uploaded_file($newfile) {
3627     if (empty($newfile)) {
3628         return '';
3629     }
3630     if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
3631         return $newfile['tmp_name'];
3632     } else {
3633         return '';
3634     }
3637 /**
3638  * Returns the maximum size for uploading files.
3639  *
3640  * There are seven possible upload limits:
3641  * 1. in Apache using LimitRequestBody (no way of checking or changing this)
3642  * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
3643  * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
3644  * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
3645  * 5. by the Moodle admin in $CFG->maxbytes
3646  * 6. by the teacher in the current course $course->maxbytes
3647  * 7. by the teacher for the current module, eg $assignment->maxbytes
3648  *
3649  * These last two are passed to this function as arguments (in bytes).
3650  * Anything defined as 0 is ignored.
3651  * The smallest of all the non-zero numbers is returned.
3652  *
3653  * @param int $sizebytes ?
3654  * @param int $coursebytes Current course $course->maxbytes (in bytes)
3655  * @param int $modulebytes Current module ->maxbytes (in bytes)
3656  * @return int The maximum size for uploading files.
3657  * @todo Finish documenting this function
3658  */
3659 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
3661     if (! $filesize = ini_get('upload_max_filesize')) {
3662         $filesize = '5M';
3663     }
3664     $minimumsize = get_real_size($filesize);
3666     if ($postsize = ini_get('post_max_size')) {
3667         $postsize = get_real_size($postsize);
3668         if ($postsize < $minimumsize) {
3669             $minimumsize = $postsize;
3670         }
3671     }
3673     if ($sitebytes and $sitebytes < $minimumsize) {
3674         $minimumsize = $sitebytes;
3675     }
3677     if ($coursebytes and $coursebytes < $minimumsize) {
3678         $minimumsize = $coursebytes;
3679     }
3681     if ($modulebytes and $modulebytes < $minimumsize) {
3682         $minimumsize = $modulebytes;
3683     }
3685     return $minimumsize;
3688 /**
3689  * Related to {@link get_max_upload_file_size()} - this function returns an
3690  * array of possible sizes in an array, translated to the
3691  * local language.
3692  *
3693  * @uses SORT_NUMERIC
3694  * @param int $sizebytes ?
3695  * @param int $coursebytes Current course $course->maxbytes (in bytes)
3696  * @param int $modulebytes Current module ->maxbytes (in bytes)
3697  * @return int
3698  * @todo Finish documenting this function
3699  */
3700 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
3702     if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
3703         return array();
3704     }
3706     $filesize[$maxsize] = display_size($maxsize);
3708     $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
3709                       5242880, 10485760, 20971520, 52428800, 104857600);
3711     foreach ($sizelist as $sizebytes) {
3712        if ($sizebytes < $maxsize) {
3713            $filesize[$sizebytes] = display_size($sizebytes);
3714        }
3715     }