MDL-6249 - easier way for human beings (as opposed to network engineers) to specify...
[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_ALPHA - contains only english letters.
120  */
121 define('PARAM_ALPHA',    0x0004);
123 /**
124  * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
125  * @TODO: should we alias it to PARAM_ALPHANUM ?
126  */
127 define('PARAM_ACTION',   0x0004);
129 /**
130  * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
131  * @TODO: should we alias it to PARAM_ALPHANUM ?
132  */
133 define('PARAM_FORMAT',   0x0004);
135 /**
136  * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
137  */
138 define('PARAM_NOTAGS',   0x0008);
140  /**
141  * PARAM_MULTILANG - alias of PARAM_TEXT.
142  */
143 define('PARAM_MULTILANG',  0x0009);
145  /**
146  * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
147  */
148 define('PARAM_TEXT',  0x0009);
150 /**
151  * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
152  */
153 define('PARAM_FILE',     0x0010);
155 /**
156  * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
157  * note: the leading slash is not removed, window drive letter is not allowed
158  */
159 define('PARAM_PATH',     0x0020);
161 /**
162  * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
163  */
164 define('PARAM_HOST',     0x0040);
166 /**
167  * PARAM_URL - expected properly formatted URL.
168  */
169 define('PARAM_URL',      0x0080);
171 /**
172  * 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!)
173  */
174 define('PARAM_LOCALURL', 0x0180);
176 /**
177  * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
178  * use when you want to store a new file submitted by students
179  */
180 define('PARAM_CLEANFILE',0x0200);
182 /**
183  * PARAM_ALPHANUM - expected numbers and letters only.
184  */
185 define('PARAM_ALPHANUM', 0x0400);
187 /**
188  * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
189  */
190 define('PARAM_BOOL',     0x0800);
192 /**
193  * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
194  * note: do not forget to addslashes() before storing into database!
195  */
196 define('PARAM_CLEANHTML',0x1000);
198 /**
199  * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
200  * suitable for include() and require()
201  * @TODO: should we rename this function to PARAM_SAFEDIRS??
202  */
203 define('PARAM_ALPHAEXT', 0x2000);
205 /**
206  * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
207  */
208 define('PARAM_SAFEDIR',  0x4000);
210 /**
211  * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9.  Numbers and comma only.
212  */
213 define('PARAM_SEQUENCE',  0x8000);
215 /// Page types ///
216 /**
217  * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
218  */
219 define('PAGE_COURSE_VIEW', 'course-view');
221 /// Debug levels ///
222 /** no warnings at all */
223 define ('DEBUG_NONE', 0);
224 /** E_ERROR | E_PARSE */
225 define ('DEBUG_MINIMAL', 5);
226 /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
227 define ('DEBUG_NORMAL', 15);
228 /** E_ALL without E_STRICT and E_RECOVERABLE_ERROR for now */
229 define ('DEBUG_ALL', 2047);
230 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
231 define ('DEBUG_DEVELOPER', 34815);
233 /// PARAMETER HANDLING ////////////////////////////////////////////////////
235 /**
236  * Returns a particular value for the named variable, taken from
237  * POST or GET.  If the parameter doesn't exist then an error is
238  * thrown because we require this variable.
239  *
240  * This function should be used to initialise all required values
241  * in a script that are based on parameters.  Usually it will be
242  * used like this:
243  *    $id = required_param('id');
244  *
245  * @param string $parname the name of the page parameter we want
246  * @param int $type expected type of parameter
247  * @return mixed
248  */
249 function required_param($parname, $type=PARAM_CLEAN) {
251     // detect_unchecked_vars addition
252     global $CFG;
253     if (!empty($CFG->detect_unchecked_vars)) {
254         global $UNCHECKED_VARS;
255         unset ($UNCHECKED_VARS->vars[$parname]);
256     }
258     if (isset($_POST[$parname])) {       // POST has precedence
259         $param = $_POST[$parname];
260     } else if (isset($_GET[$parname])) {
261         $param = $_GET[$parname];
262     } else {
263         error('A required parameter ('.$parname.') was missing');
264     }
266     return clean_param($param, $type);
269 /**
270  * Returns a particular value for the named variable, taken from
271  * POST or GET, otherwise returning a given default.
272  *
273  * This function should be used to initialise all optional values
274  * in a script that are based on parameters.  Usually it will be
275  * used like this:
276  *    $name = optional_param('name', 'Fred');
277  *
278  * @param string $parname the name of the page parameter we want
279  * @param mixed  $default the default value to return if nothing is found
280  * @param int $type expected type of parameter
281  * @return mixed
282  */
283 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
285     // detect_unchecked_vars addition
286     global $CFG;
287     if (!empty($CFG->detect_unchecked_vars)) {
288         global $UNCHECKED_VARS;
289         unset ($UNCHECKED_VARS->vars[$parname]);
290     }
292     if (isset($_POST[$parname])) {       // POST has precedence
293         $param = $_POST[$parname];
294     } else if (isset($_GET[$parname])) {
295         $param = $_GET[$parname];
296     } else {
297         return $default;
298     }
300     return clean_param($param, $type);
303 /**
304  * Used by {@link optional_param()} and {@link required_param()} to
305  * clean the variables and/or cast to specific types, based on
306  * an options field.
307  * <code>
308  * $course->format = clean_param($course->format, PARAM_ALPHA);
309  * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
310  * </code>
311  *
312  * @uses $CFG
313  * @uses PARAM_CLEAN
314  * @uses PARAM_INT
315  * @uses PARAM_INTEGER
316  * @uses PARAM_ALPHA
317  * @uses PARAM_ALPHANUM
318  * @uses PARAM_NOTAGS
319  * @uses PARAM_ALPHAEXT
320  * @uses PARAM_BOOL
321  * @uses PARAM_SAFEDIR
322  * @uses PARAM_CLEANFILE
323  * @uses PARAM_FILE
324  * @uses PARAM_PATH
325  * @uses PARAM_HOST
326  * @uses PARAM_URL
327  * @uses PARAM_LOCALURL
328  * @uses PARAM_CLEANHTML
329  * @uses PARAM_SEQUENCE
330  * @param mixed $param the variable we are cleaning
331  * @param int $type expected format of param after cleaning.
332  * @return mixed
333  */
334 function clean_param($param, $type) {
336     global $CFG;
338     if (is_array($param)) {              // Let's loop
339         $newparam = array();
340         foreach ($param as $key => $value) {
341             $newparam[$key] = clean_param($value, $type);
342         }
343         return $newparam;
344     }
346     switch ($type) {
347         case PARAM_RAW:          // no cleaning at all
348             return $param;
350         case PARAM_CLEAN:        // General HTML cleaning, try to use more specific type if possible
351             if (is_numeric($param)) {
352                 return $param;
353             }
354             $param = stripslashes($param);   // Needed for kses to work fine
355             $param = clean_text($param);     // Sweep for scripts, etc
356             return addslashes($param);       // Restore original request parameter slashes
358         case PARAM_CLEANHTML:    // prepare html fragment for display, do not store it into db!!
359             $param = stripslashes($param);   // Remove any slashes
360             $param = clean_text($param);     // Sweep for scripts, etc
361             return trim($param);
363         case PARAM_INT:
364             return (int)$param;  // Convert to integer
366         case PARAM_ALPHA:        // Remove everything not a-z
367             return eregi_replace('[^a-zA-Z]', '', $param);
369         case PARAM_ALPHANUM:     // Remove everything not a-zA-Z0-9
370             return eregi_replace('[^A-Za-z0-9]', '', $param);
372         case PARAM_ALPHAEXT:     // Remove everything not a-zA-Z/_-
373             return eregi_replace('[^a-zA-Z/_-]', '', $param);
375         case PARAM_SEQUENCE:     // Remove everything not 0-9,
376             return eregi_replace('[^0-9,]', '', $param);
378         case PARAM_BOOL:         // Convert to 1 or 0
379             $tempstr = strtolower($param);
380             if ($tempstr == 'on' or $tempstr == 'yes' ) {
381                 $param = 1;
382             } else if ($tempstr == 'off' or $tempstr == 'no') {
383                 $param = 0;
384             } else {
385                 $param = empty($param) ? 0 : 1;
386             }
387             return $param;
389         case PARAM_NOTAGS:       // Strip all tags
390             return strip_tags($param);
392         case PARAM_TEXT:    // leave only tags needed for multilang
393             return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
395         case PARAM_SAFEDIR:      // Remove everything not a-zA-Z0-9_-
396             return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
398         case PARAM_CLEANFILE:    // allow only safe characters
399             return clean_filename($param);
401         case PARAM_FILE:         // Strip all suspicious characters from filename
402             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
403             $param = ereg_replace('\.\.+', '', $param);
404             if($param == '.') {
405                 $param = '';
406             }
407             return $param;
409         case PARAM_PATH:         // Strip all suspicious characters from file path
410             $param = str_replace('\\\'', '\'', $param);
411             $param = str_replace('\\"', '"', $param);
412             $param = str_replace('\\', '/', $param);
413             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
414             $param = ereg_replace('\.\.+', '', $param);
415             $param = ereg_replace('//+', '/', $param);
416             return ereg_replace('/(\./)+', '/', $param);
418         case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
419             preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
420             // match ipv4 dotted quad
421             if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
422                 // confirm values are ok
423                 if ( $match[0] > 255
424                      || $match[1] > 255
425                      || $match[3] > 255
426                      || $match[4] > 255 ) {
427                     // hmmm, what kind of dotted quad is this?
428                     $param = '';
429                 }
430             } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
431                        && !preg_match('/^[\.-]/',  $param) // no leading dots/hyphens
432                        && !preg_match('/[\.-]$/',  $param) // no trailing dots/hyphens
433                        ) {
434                 // all is ok - $param is respected
435             } else {
436                 // all is not ok...
437                 $param='';
438             }
439             return $param;
441         case PARAM_URL:          // allow safe ftp, http, mailto urls
442             include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
443             if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p-f?q?r?')) {
444                 // all is ok, param is respected
445             } else {
446                 $param =''; // not really ok
447             }
448             return $param;
450         case PARAM_LOCALURL:     // allow http absolute, root relative and relative URLs within wwwroot
451             clean_param($param, PARAM_URL);
452             if (!empty($param)) {
453                 if (preg_match(':^/:', $param)) {
454                     // root-relative, ok!
455                 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
456                     // absolute, and matches our wwwroot
457                 } else {
458                     // relative - let's make sure there are no tricks
459                     if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
460                         // looks ok.
461                     } else {
462                         $param = '';
463                     }
464                 }
465             }
466             return $param;
468         default:                 // throw error, switched parameters in optional_param or another serious problem
469             error("Unknown parameter type: $type");
470     }
475 /**
476  * Set a key in global configuration
477  *
478  * Set a key/value pair in both this session's {@link $CFG} global variable
479  * and in the 'config' database table for future sessions.
480  *
481  * Can also be used to update keys for plugin-scoped configs in config_plugin table.
482  * In that case it doesn't affect $CFG.
483  *
484  * @param string $name the key to set
485  * @param string $value the value to set
486  * @param string $plugin (optional) the plugin scope
487  * @uses $CFG
488  * @return bool
489  */
490 function set_config($name, $value, $plugin=NULL) {
491 /// No need for get_config because they are usually always available in $CFG
493     global $CFG;
495     if (empty($plugin)) {
496         $CFG->$name = $value;  // So it's defined for this invocation at least
498         if (get_field('config', 'name', 'name', $name)) {
499             return set_field('config', 'value', $value, 'name', $name);
500         } else {
501             $config->name = $name;
502             $config->value = $value;
503             return insert_record('config', $config);
504         }
505     } else { // plugin scope
506         if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
507             return set_field('config_plugins', 'value', $value, 'id', $id);
508         } else {
509             $config->plugin = $plugin;
510             $config->name   = $name;
511             $config->value  = $value;
512             return insert_record('config_plugins', $config);
513         }
514     }
517 /**
518  * Get configuration values from the global config table
519  * or the config_plugins table.
520  *
521  * If called with no parameters it will do the right thing
522  * generating $CFG safely from the database without overwriting
523  * existing values.
524  *
525  * @param string $plugin
526  * @param string $name
527  * @uses $CFG
528  * @return hash-like object or single value
529  *
530  */
531 function get_config($plugin=NULL, $name=NULL) {
533     global $CFG;
535     if (!empty($name)) { // the user is asking for a specific value
536         if (!empty($plugin)) {
537             return get_record('config_plugins', 'plugin' , $plugin, 'name', $name);
538         } else {
539             return get_record('config', 'name', $name);
540         }
541     }
543     // the user is after a recordset
544     if (!empty($plugin)) {
545         if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
546             $configs = (array)$configs;
547             $localcfg = array();
548             foreach ($configs as $config) {
549                 $localcfg[$config->name] = $config->value;
550             }
551             return (object)$localcfg;
552         } else {
553             return false;
554         }
555     } else {
556         // this was originally in setup.php
557         if ($configs = get_records('config')) {
558             $localcfg = (array)$CFG;
559             foreach ($configs as $config) {
560                 if (!isset($localcfg[$config->name])) {
561                     $localcfg[$config->name] = $config->value;
562                 } else {
563                     if ($localcfg[$config->name] != $config->value ) {
564                         // complain if the DB has a different
565                         // value than config.php does
566                         error_log("\$CFG->{$config->name} in config.php ({$localcfg[$config->name]}) overrides database setting ({$config->value})");
567                     }
568                 }
569             }
571             $localcfg = (object)$localcfg;
572             return $localcfg;
573         } else {
574             // preserve $CFG if DB returns nothing or error
575             return $CFG;
576         }
578     }
581 /**
582  * Removes a key from global configuration
583  *
584  * @param string $name the key to set
585  * @param string $plugin (optional) the plugin scope
586  * @uses $CFG
587  * @return bool
588  */
589 function unset_config($name, $plugin=NULL) {
591     global $CFG;
593     unset($CFG->$name);
595     if (empty($plugin)) {
596         return delete_records('config', 'name', $name);
597     } else { 
598         return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
599     }
603 /**
604  * Refresh current $USER session global variable with all their current preferences.
605  * @uses $USER
606  */
607 function reload_user_preferences() {
609     global $USER;
611     if(empty($USER) || empty($USER->id)) {
612         return false;
613     }
615     unset($USER->preference);
617     if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
618         foreach ($preferences as $preference) {
619             $USER->preference[$preference->name] = $preference->value;
620         }
621     } else {
622             //return empty preference array to hold new values
623             $USER->preference = array();
624     }
627 /**
628  * Sets a preference for the current user
629  * Optionally, can set a preference for a different user object
630  * @uses $USER
631  * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
633  * @param string $name The key to set as preference for the specified user
634  * @param string $value The value to set forthe $name key in the specified user's record
635  * @param int $userid A moodle user ID
636  * @return bool
637  */
638 function set_user_preference($name, $value, $otheruser=NULL) {
640     global $USER;
642     if (empty($otheruser)){
643         if (!empty($USER) && !empty($USER->id)) {
644             $userid = $USER->id;
645         } else {
646             return false;
647         }
648     } else {
649         $userid = $otheruser;
650     }
652     if (empty($name)) {
653         return false;
654     }
656     if ($preference = get_record('user_preferences', 'userid', $userid, 'name', $name)) {
657         if (set_field('user_preferences', 'value', $value, 'id', $preference->id)) {
658             if (empty($otheruser) and !empty($USER)) {
659                 $USER->preference[$name] = $value;
660             }
661             return true;
662         } else {
663             return false;
664         }
666     } else {
667         $preference->userid = $userid;
668         $preference->name   = $name;
669         $preference->value  = (string)$value;
670         if (insert_record('user_preferences', $preference)) {
671             if (empty($otheruser) and !empty($USER)) {
672                 $USER->preference[$name] = $value;
673             }
674             return true;
675         } else {
676             return false;
677         }
678     }
681 /**
682  * Unsets a preference completely by deleting it from the database
683  * Optionally, can set a preference for a different user id
684  * @uses $USER
685  * @param string  $name The key to unset as preference for the specified user
686  * @param int $userid A moodle user ID
687  * @return bool
688  */
689 function unset_user_preference($name, $userid=NULL) {
691     global $USER;
693     if (empty($userid)){
694         if(!empty($USER) && !empty($USER->id)) {
695             $userid = $USER->id;
696         }
697         else {
698             return false;
699         }
700     }
702     //Delete the preference from $USER
703     if (isset($USER->preference[$name])) {
704         unset($USER->preference[$name]);
705     }
707     //Then from DB
708     return delete_records('user_preferences', 'userid', $userid, 'name', $name);
712 /**
713  * Sets a whole array of preferences for the current user
714  * @param array $prefarray An array of key/value pairs to be set
715  * @param int $userid A moodle user ID
716  * @return bool
717  */
718 function set_user_preferences($prefarray, $userid=NULL) {
720     global $USER;
722     if (!is_array($prefarray) or empty($prefarray)) {
723         return false;
724     }
726     if (empty($userid)){
727         if (!empty($USER) && !empty($USER->id)) {
728             $userid = NULL;  // Continue with the current user below
729         } else {
730             return false;    // No-one to set!
731         }
732     }
734     $return = true;
735     foreach ($prefarray as $name => $value) {
736         // The order is important; if the test for return is done first, then
737         // if one function call fails all the remaining ones will be "optimized away"
738         $return = set_user_preference($name, $value, $userid) and $return;
739     }
740     return $return;
743 /**
744  * If no arguments are supplied this function will return
745  * all of the current user preferences as an array.
746  * If a name is specified then this function
747  * attempts to return that particular preference value.  If
748  * none is found, then the optional value $default is returned,
749  * otherwise NULL.
750  * @param string $name Name of the key to use in finding a preference value
751  * @param string $default Value to be returned if the $name key is not set in the user preferences
752  * @param int $userid A moodle user ID
753  * @uses $USER
754  * @return string
755  */
756 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
758     global $USER;
760     if (empty($userid)) {   // assume current user
761         if (empty($USER->preference)) {
762             return $default;              // Default value (or NULL)
763         }
764         if (empty($name)) {
765             return $USER->preference;     // Whole array
766         }
767         if (!isset($USER->preference[$name])) {
768             return $default;              // Default value (or NULL)
769         }
770         return $USER->preference[$name];  // The single value
772     } else {
773         $preference = get_records_menu('user_preferences', 'userid', $userid, 'name', 'name,value');
775         if (empty($name)) {
776             return $preference;
777         }
778         if (!isset($preference[$name])) {
779             return $default;              // Default value (or NULL)
780         }
781         return $preference[$name];        // The single value
782     }
786 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
788 /**
789  * Given date parts in user time produce a GMT timestamp.
790  *
791  * @param int $year The year part to create timestamp of
792  * @param int $month The month part to create timestamp of
793  * @param int $day The day part to create timestamp of
794  * @param int $hour The hour part to create timestamp of
795  * @param int $minute The minute part to create timestamp of
796  * @param int $second The second part to create timestamp of
797  * @param float $timezone ?
798  * @param bool $applydst ?
799  * @return int timestamp
800  * @todo Finish documenting this function
801  */
802 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
804     $timezone = get_user_timezone_offset($timezone);
806     if (abs($timezone) > 13) {
807         $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
808     } else {
809         $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
810         $time = usertime($time, $timezone);
811         if($applydst) {
812             $time -= dst_offset_on($time);
813         }
814     }
816     return $time;
820 /**
821  * Given an amount of time in seconds, returns string
822  * formatted nicely as months, days, hours etc as needed
823  *
824  * @uses MINSECS
825  * @uses HOURSECS
826  * @uses DAYSECS
827  * @param int $totalsecs ?
828  * @param array $str ?
829  * @return string
830  * @todo Finish documenting this function
831  */
832  function format_time($totalsecs, $str=NULL) {
834     $totalsecs = abs($totalsecs);
836     if (!$str) {  // Create the str structure the slow way
837         $str->day   = get_string('day');
838         $str->days  = get_string('days');
839         $str->hour  = get_string('hour');
840         $str->hours = get_string('hours');
841         $str->min   = get_string('min');
842         $str->mins  = get_string('mins');
843         $str->sec   = get_string('sec');
844         $str->secs  = get_string('secs');
845     }
847     $days      = floor($totalsecs/DAYSECS);
848     $remainder = $totalsecs - ($days*DAYSECS);
849     $hours     = floor($remainder/HOURSECS);
850     $remainder = $remainder - ($hours*HOURSECS);
851     $mins      = floor($remainder/MINSECS);
852     $secs      = $remainder - ($mins*MINSECS);
854     $ss = ($secs == 1)  ? $str->sec  : $str->secs;
855     $sm = ($mins == 1)  ? $str->min  : $str->mins;
856     $sh = ($hours == 1) ? $str->hour : $str->hours;
857     $sd = ($days == 1)  ? $str->day  : $str->days;
859     $odays = '';
860     $ohours = '';
861     $omins = '';
862     $osecs = '';
864     if ($days)  $odays  = $days .' '. $sd;
865     if ($hours) $ohours = $hours .' '. $sh;
866     if ($mins)  $omins  = $mins .' '. $sm;
867     if ($secs)  $osecs  = $secs .' '. $ss;
869     if ($days)  return $odays .' '. $ohours;
870     if ($hours) return $ohours .' '. $omins;
871     if ($mins)  return $omins .' '. $osecs;
872     if ($secs)  return $osecs;
873     return get_string('now');
876 /**
877  * Returns a formatted string that represents a date in user time
878  * <b>WARNING: note that the format is for strftime(), not date().</b>
879  * Because of a bug in most Windows time libraries, we can't use
880  * the nicer %e, so we have to use %d which has leading zeroes.
881  * A lot of the fuss in the function is just getting rid of these leading
882  * zeroes as efficiently as possible.
883  *
884  * If parameter fixday = true (default), then take off leading
885  * zero from %d, else mantain it.
886  *
887  * @uses HOURSECS
888  * @param  int $date timestamp in GMT
889  * @param string $format strftime format
890  * @param float $timezone
891  * @param bool $fixday If true (default) then the leading
892  * zero from %d is removed. If false then the leading zero is mantained.
893  * @return string
894  */
895 function userdate($date, $format='', $timezone=99, $fixday = true) {
897     global $CFG;
899     static $strftimedaydatetime;
901     if ($format == '') {
902         if (empty($strftimedaydatetime)) {
903             $strftimedaydatetime = get_string('strftimedaydatetime');
904         }
905         $format = $strftimedaydatetime;
906     }
908     if (!empty($CFG->nofixday)) {  // Config.php can force %d not to be fixed.
909         $fixday = false;
910     } else if ($fixday) {
911         $formatnoday = str_replace('%d', 'DD', $format);
912         $fixday = ($formatnoday != $format);
913     }
915     $date += dst_offset_on($date);
917     $timezone = get_user_timezone_offset($timezone);
919     if (abs($timezone) > 13) {   /// Server time
920         if ($fixday) {
921             $datestring = strftime($formatnoday, $date);
922             $daystring  = str_replace(' 0', '', strftime(' %d', $date));
923             $datestring = str_replace('DD', $daystring, $datestring);
924         } else {
925             $datestring = strftime($format, $date);
926         }
927     } else {
928         $date += (int)($timezone * 3600);
929         if ($fixday) {
930             $datestring = gmstrftime($formatnoday, $date);
931             $daystring  = str_replace(' 0', '', gmstrftime(' %d', $date));
932             $datestring = str_replace('DD', $daystring, $datestring);
933         } else {
934             $datestring = gmstrftime($format, $date);
935         }
936     }
938 /// If we are running under Windows and unicode is enabled, try to convert the datestring
939 /// to current_charset() (because it's impossible to specify UTF-8 to fetch locale info in Win32)
941    if (!empty($CFG->unicodedb) && $CFG->ostype == 'WINDOWS') {
942        if ($localewincharset = get_string('localewincharset')) {
943            $textlib = textlib_get_instance();
944            $datestring = $textlib->convert($datestring, $localewincharset, current_charset());
945        }
946    }
948     return $datestring;
951 /**
952  * Given a $time timestamp in GMT (seconds since epoch),
953  * returns an array that represents the date in user time
954  *
955  * @uses HOURSECS
956  * @param int $time Timestamp in GMT
957  * @param float $timezone ?
958  * @return array An array that represents the date in user time
959  * @todo Finish documenting this function
960  */
961 function usergetdate($time, $timezone=99) {
963     $timezone = get_user_timezone_offset($timezone);
965     if (abs($timezone) > 13) {    // Server time
966         return getdate($time);
967     }
969     // There is no gmgetdate so we use gmdate instead
970     $time += dst_offset_on($time);
971     $time += intval((float)$timezone * HOURSECS);
973     $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
975     list(
976         $getdate['seconds'],
977         $getdate['minutes'],
978         $getdate['hours'],
979         $getdate['mday'],
980         $getdate['mon'],
981         $getdate['year'],
982         $getdate['wday'],
983         $getdate['yday'],
984         $getdate['weekday'],
985         $getdate['month']
986     ) = explode('_', $datestring);
988     return $getdate;
991 /**
992  * Given a GMT timestamp (seconds since epoch), offsets it by
993  * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
994  *
995  * @uses HOURSECS
996  * @param  int $date Timestamp in GMT
997  * @param float $timezone
998  * @return int
999  */
1000 function usertime($date, $timezone=99) {
1002     $timezone = get_user_timezone_offset($timezone);
1004     if (abs($timezone) > 13) {
1005         return $date;
1006     }
1007     return $date - (int)($timezone * HOURSECS);
1010 /**
1011  * Given a time, return the GMT timestamp of the most recent midnight
1012  * for the current user.
1013  *
1014  * @param int $date Timestamp in GMT
1015  * @param float $timezone ?
1016  * @return ?
1017  */
1018 function usergetmidnight($date, $timezone=99) {
1020     $timezone = get_user_timezone_offset($timezone);
1021     $userdate = usergetdate($date, $timezone);
1023     // Time of midnight of this user's day, in GMT
1024     return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1028 /**
1029  * Returns a string that prints the user's timezone
1030  *
1031  * @param float $timezone The user's timezone
1032  * @return string
1033  */
1034 function usertimezone($timezone=99) {
1036     $tz = get_user_timezone($timezone);
1038     if (!is_float($tz)) {
1039         return $tz;
1040     }
1042     if(abs($tz) > 13) { // Server time
1043         return get_string('serverlocaltime');
1044     }
1046     if($tz == intval($tz)) {
1047         // Don't show .0 for whole hours
1048         $tz = intval($tz);
1049     }
1051     if($tz == 0) {
1052         return 'GMT';
1053     }
1054     else if($tz > 0) {
1055         return 'GMT+'.$tz;
1056     }
1057     else {
1058         return 'GMT'.$tz;
1059     }
1063 /**
1064  * Returns a float which represents the user's timezone difference from GMT in hours
1065  * Checks various settings and picks the most dominant of those which have a value
1066  *
1067  * @uses $CFG
1068  * @uses $USER
1069  * @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
1070  * @return int
1071  */
1072 function get_user_timezone_offset($tz = 99) {
1074     global $USER, $CFG;
1076     $tz = get_user_timezone($tz);
1078     if (is_float($tz)) {
1079         return $tz;
1080     } else {
1081         $tzrecord = get_timezone_record($tz);
1082         if (empty($tzrecord)) {
1083             return 99.0;
1084         }
1085         return (float)$tzrecord->gmtoff / HOURMINS;
1086     }
1089 /**
1090  * Returns a float or a string which denotes the user's timezone
1091  * 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)
1092  * means that for this timezone there are also DST rules to be taken into account
1093  * Checks various settings and picks the most dominant of those which have a value
1094  *
1095  * @uses $USER
1096  * @uses $CFG
1097  * @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
1098  * @return mixed
1099  */
1100 function get_user_timezone($tz = 99) {
1101     global $USER, $CFG;
1103     $timezones = array(
1104         $tz,
1105         isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1106         isset($USER->timezone) ? $USER->timezone : 99,
1107         isset($CFG->timezone) ? $CFG->timezone : 99,
1108         );
1110     $tz = 99;
1112     while(($tz == '' || $tz == 99) && $next = each($timezones)) {
1113         $tz = $next['value'];
1114     }
1116     return is_numeric($tz) ? (float) $tz : $tz;
1119 /**
1120  * ?
1121  *
1122  * @uses $CFG
1123  * @uses $db
1124  * @param string $timezonename ?
1125  * @return object
1126  */
1127 function get_timezone_record($timezonename) {
1128     global $CFG, $db;
1129     static $cache = NULL;
1131     if ($cache === NULL) {
1132         $cache = array();
1133     }
1135     if (isset($cache[$timezonename])) {
1136         return $cache[$timezonename];
1137     }
1139     return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1140                                       WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1143 /**
1144  * ?
1145  *
1146  * @uses $CFG
1147  * @uses $USER
1148  * @param ? $fromyear ?
1149  * @param ? $to_year ?
1150  * @return bool
1151  */
1152 function calculate_user_dst_table($from_year = NULL, $to_year = NULL) {
1153     global $CFG, $SESSION;
1155     $usertz = get_user_timezone();
1157     if (is_float($usertz)) {
1158         // Trivial timezone, no DST
1159         return false;
1160     }
1162     if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1163         // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1164         unset($SESSION->dst_offsets);
1165         unset($SESSION->dst_range);
1166     }
1168     if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1169         // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1170         // This will be the return path most of the time, pretty light computationally
1171         return true;
1172     }
1174     // Reaching here means we either need to extend our table or create it from scratch
1176     // Remember which TZ we calculated these changes for
1177     $SESSION->dst_offsettz = $usertz;
1179     if(empty($SESSION->dst_offsets)) {
1180         // If we 're creating from scratch, put the two guard elements in there
1181         $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1182     }
1183     if(empty($SESSION->dst_range)) {
1184         // If creating from scratch
1185         $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1186         $to   = min((empty($to_year)   ? intval(date('Y')) + 3 : $to_year),   2035);
1188         // Fill in the array with the extra years we need to process
1189         $yearstoprocess = array();
1190         for($i = $from; $i <= $to; ++$i) {
1191             $yearstoprocess[] = $i;
1192         }
1194         // Take note of which years we have processed for future calls
1195         $SESSION->dst_range = array($from, $to);
1196     }
1197     else {
1198         // If needing to extend the table, do the same
1199         $yearstoprocess = array();
1201         $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1202         $to   = min((empty($to_year)   ? $SESSION->dst_range[1] : $to_year),   2035);
1204         if($from < $SESSION->dst_range[0]) {
1205             // Take note of which years we need to process and then note that we have processed them for future calls
1206             for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1207                 $yearstoprocess[] = $i;
1208             }
1209             $SESSION->dst_range[0] = $from;
1210         }
1211         if($to > $SESSION->dst_range[1]) {
1212             // Take note of which years we need to process and then note that we have processed them for future calls
1213             for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1214                 $yearstoprocess[] = $i;
1215             }
1216             $SESSION->dst_range[1] = $to;
1217         }
1218     }
1220     if(empty($yearstoprocess)) {
1221         // This means that there was a call requesting a SMALLER range than we have already calculated
1222         return true;
1223     }
1225     // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1226     // Also, the array is sorted in descending timestamp order!
1228     // Get DB data
1229     $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');
1230     if(empty($presetrecords)) {
1231         return false;
1232     }
1234     // Remove ending guard (first element of the array)
1235     reset($SESSION->dst_offsets);
1236     unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1238     // Add all required change timestamps
1239     foreach($yearstoprocess as $y) {
1240         // Find the record which is in effect for the year $y
1241         foreach($presetrecords as $year => $preset) {
1242             if($year <= $y) {
1243                 break;
1244             }
1245         }
1247         $changes = dst_changes_for_year($y, $preset);
1249         if($changes === NULL) {
1250             continue;
1251         }
1252         if($changes['dst'] != 0) {
1253             $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1254         }
1255         if($changes['std'] != 0) {
1256             $SESSION->dst_offsets[$changes['std']] = 0;
1257         }
1258     }
1260     // Put in a guard element at the top
1261     $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1262     $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1264     // Sort again
1265     krsort($SESSION->dst_offsets);
1267     return true;
1270 function dst_changes_for_year($year, $timezone) {
1272     if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1273         return NULL;
1274     }
1276     $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1277     $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1279     list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1280     list($std_hour, $std_min) = explode(':', $timezone->std_time);
1282     $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1283     $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1285     // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1286     // This has the advantage of being able to have negative values for hour, i.e. for timezones
1287     // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1289     $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1290     $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1292     return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1295 // $time must NOT be compensated at all, it has to be a pure timestamp
1296 function dst_offset_on($time) {
1297     global $SESSION;
1299     if(!calculate_user_dst_table() || empty($SESSION->dst_offsets)) {
1300         return 0;
1301     }
1303     reset($SESSION->dst_offsets);
1304     while(list($from, $offset) = each($SESSION->dst_offsets)) {
1305         if($from <= $time) {
1306             break;
1307         }
1308     }
1310     // This is the normal return path
1311     if($offset !== NULL) {
1312         return $offset;
1313     }
1315     // Reaching this point means we haven't calculated far enough, do it now:
1316     // Calculate extra DST changes if needed and recurse. The recursion always
1317     // moves toward the stopping condition, so will always end.
1319     if($from == 0) {
1320         // We need a year smaller than $SESSION->dst_range[0]
1321         if($SESSION->dst_range[0] == 1971) {
1322             return 0;
1323         }
1324         calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL);
1325         return dst_offset_on($time);
1326     }
1327     else {
1328         // We need a year larger than $SESSION->dst_range[1]
1329         if($SESSION->dst_range[1] == 2035) {
1330             return 0;
1331         }
1332         calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5);
1333         return dst_offset_on($time);
1334     }
1337 function find_day_in_month($startday, $weekday, $month, $year) {
1339     $daysinmonth = days_in_month($month, $year);
1341     if($weekday == -1) {
1342         // Don't care about weekday, so return:
1343         //    abs($startday) if $startday != -1
1344         //    $daysinmonth otherwise
1345         return ($startday == -1) ? $daysinmonth : abs($startday);
1346     }
1348     // From now on we 're looking for a specific weekday
1350     // Give "end of month" its actual value, since we know it
1351     if($startday == -1) {
1352         $startday = -1 * $daysinmonth;
1353     }
1355     // Starting from day $startday, the sign is the direction
1357     if($startday < 1) {
1359         $startday = abs($startday);
1360         $lastmonthweekday  = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1362         // This is the last such weekday of the month
1363         $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1364         if($lastinmonth > $daysinmonth) {
1365             $lastinmonth -= 7;
1366         }
1368         // Find the first such weekday <= $startday
1369         while($lastinmonth > $startday) {
1370             $lastinmonth -= 7;
1371         }
1373         return $lastinmonth;
1375     }
1376     else {
1378         $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1380         $diff = $weekday - $indexweekday;
1381         if($diff < 0) {
1382             $diff += 7;
1383         }
1385         // This is the first such weekday of the month equal to or after $startday
1386         $firstfromindex = $startday + $diff;
1388         return $firstfromindex;
1390     }
1393 /**
1394  * Calculate the number of days in a given month
1395  *
1396  * @param int $month The month whose day count is sought
1397  * @param int $year The year of the month whose day count is sought
1398  * @return int
1399  */
1400 function days_in_month($month, $year) {
1401    return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1404 /**
1405  * Calculate the position in the week of a specific calendar day
1406  *
1407  * @param int $day The day of the date whose position in the week is sought
1408  * @param int $month The month of the date whose position in the week is sought
1409  * @param int $year The year of the date whose position in the week is sought
1410  * @return int
1411  */
1412 function dayofweek($day, $month, $year) {
1413     // I wonder if this is any different from
1414     // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1415     return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1418 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1420 /**
1421  * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1422  * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1423  * sesskey string if $USER exists, or boolean false if not.
1424  *
1425  * @uses $USER
1426  * @return string
1427  */
1428 function sesskey() {
1429     global $USER;
1431     if(!isset($USER)) {
1432         return false;
1433     }
1435     if (empty($USER->sesskey)) {
1436         $USER->sesskey = random_string(10);
1437     }
1439     return $USER->sesskey;
1443 /**
1444  * For security purposes, this function will check that the currently
1445  * given sesskey (passed as a parameter to the script or this function)
1446  * matches that of the current user.
1447  *
1448  * @param string $sesskey optionally provided sesskey
1449  * @return bool
1450  */
1451 function confirm_sesskey($sesskey=NULL) {
1452     global $USER;
1454     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1455         return true;
1456     }
1458     if (empty($sesskey)) {
1459         $sesskey = required_param('sesskey', PARAM_RAW);  // Check script parameters
1460     }
1462     if (!isset($USER->sesskey)) {
1463         return false;
1464     }
1466     return ($USER->sesskey === $sesskey);
1470 /**
1471  * This function checks that the current user is logged in and has the
1472  * required privileges
1473  *
1474  * This function checks that the current user is logged in, and optionally
1475  * whether they are allowed to be in a particular course and view a particular
1476  * course module.
1477  * If they are not logged in, then it redirects them to the site login unless
1478  * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1479  * case they are automatically logged in as guests.
1480  * If $courseid is given and the user is not enrolled in that course then the
1481  * user is redirected to the course enrolment page.
1482  * If $cm is given and the coursemodule is hidden and the user is not a teacher
1483  * in the course then the user is redirected to the course home page.
1484  *
1485  * @uses $CFG
1486  * @uses $SESSION
1487  * @uses $USER
1488  * @uses $FULLME
1489  * @uses SITEID
1490  * @uses $COURSE
1491  * @uses $MoodleSession
1492  * @param int $courseid id of the course
1493  * @param bool $autologinguest
1494  * @param object $cm course module object
1495  */
1496 function require_login($courseid=0, $autologinguest=true, $cm=null) {
1498     global $CFG, $SESSION, $USER, $COURSE, $FULLME, $MoodleSession;
1500 /// Redefine global $COURSE if we can
1501     global $course;  // We use the global hack once here so it doesn't need to be used again
1502     if (is_object($course) and !empty($course->id) and ($courseid == 0 || $course->id == $courseid)) {
1503         $COURSE = clone($course);
1504     } else if ($courseid) {
1505         $COURSE = get_record('course', 'id', $courseid);
1506     }
1508     if (!empty($COURSE->lang)) {
1509         $CFG->courselang = $COURSE->lang;
1510         moodle_setlocale();
1511     }
1513 /// If the user is not even logged in yet then make sure they are
1514     if (! (isset($USER->loggedin) and $USER->confirmed and ($USER->site == $CFG->wwwroot)) ) {
1515         $SESSION->wantsurl = $FULLME;
1516         if (!empty($_SERVER['HTTP_REFERER'])) {
1517             $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
1518         }
1519         $USER = NULL;
1520         if ($autologinguest && !empty($CFG->autologinguests) and 
1521             $courseid and ($courseid == SITEID or get_field('course','guest','id',$courseid)) ) {
1522             $loginguest = '?loginguest=true';
1523         } else {
1524             $loginguest = '';
1525         }
1526         if (empty($CFG->loginhttps)) {
1527             redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1528         } else {
1529             $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1530             redirect($wwwroot .'/login/index.php'. $loginguest);
1531         }
1532         exit;
1533     }
1535 /// check whether the user should be changing password
1536     if (!empty($USER->preference['auth_forcepasswordchange'])){
1537         if (is_internal_auth() || $CFG->{'auth_'.$USER->auth.'_stdchangepassword'}){
1538             $SESSION->wantsurl = $FULLME;
1539             redirect($CFG->wwwroot .'/login/change_password.php');
1540         } elseif($CFG->changepassword) {
1541             redirect($CFG->changepassword);
1542         } else {
1543             error('You cannot proceed without changing your password.
1544                    However there is no available page for changing it.
1545                    Please contact your Moodle Administrator.');
1546         }
1547     }
1548 /// Check that the user account is properly set up
1549     if (user_not_fully_set_up($USER)) {
1550         $SESSION->wantsurl = $FULLME;
1551         redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1552     }
1554 /// Make sure current IP matches the one for this session (if required)
1555     if (!empty($CFG->tracksessionip)) {
1556         if ($USER->sessionIP != md5(getremoteaddr())) {
1557             error(get_string('sessionipnomatch', 'error'));
1558         }
1559     }
1561 /// Make sure the USER has a sesskey set up.  Used for checking script parameters.
1562     sesskey();
1564     // Check that the user has agreed to a site policy if there is one
1565     if (!empty($CFG->sitepolicy)) {
1566         if (!$USER->policyagreed) {
1567             $SESSION->wantsurl = $FULLME;
1568             redirect($CFG->wwwroot .'/user/policy.php');
1569         }
1570     }
1572 /// If the site is currently under maintenance, then print a message
1573     if (!has_capability('moodle/site:config',get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1574         if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1575             print_maintenance_message();
1576             exit;
1577         }
1578     }
1580 /// Next, check if the user can be in a particular course
1581     if ($courseid) {
1583     /// Sanity check on the courseid
1585         if ($courseid == $COURSE->id) {     /// Pretty much always true but let's be sure
1586             $course = $COURSE;
1587         } else if (! $course = get_record('course', 'id', $courseid)) {
1588             error('That course doesn\'t exist');
1589         }
1591     /// We can eliminate hidden site activities straight away
1593         if ($course->id == SITEID) {
1594             if (!empty($cm) && !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', 
1595                                                           get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1596                 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
1597             }
1598             return;
1599         }
1601  
1602     /// If the whole course is hidden from us then we can stop now
1604         if (!$context = get_context_instance(CONTEXT_COURSE, $course->id)) {
1605             print_error('nocontext');
1606         }
1608         if (empty($USER->switchrole[$context->id]) &&
1609             !($course->visible && course_parent_visible($course)) &&
1610                !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id)) ){
1611             print_header_simple();
1612             notice(get_string('coursehidden'), $CFG->wwwroot .'/');
1613         }    
1614         
1615     /// If the user is a guest then treat them according to the course policy about guests
1617         if (has_capability('moodle/legacy:guest', $context, NULL, false)) {
1618             switch ($course->guest) {    /// Check course policy about guest access
1620                 case 1:    /// Guests always allowed 
1621                     if (!has_capability('moodle/course:view', $context)) {    // Prohibited by capability
1622                         print_header_simple();
1623                         notice(get_string('guestsnotallowed', '', $course->fullname), "$CFG->wwwroot/login/index.php");
1624                     }
1625                     if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
1626                         redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, 
1627                                  get_string('activityiscurrentlyhidden'));
1628                     }
1630                     return;   // User is allowed to see this course
1632                     break;
1634                 case 2:    /// Guests allowed with key (drop through to logic below)
1635                     break;
1637                 default:    /// Guests not allowed
1638                     print_header_simple('', '', get_string('loggedinasguest'));
1639                     if (empty($USER->switchrole[$context->id])) {  // Normal guest
1640                         notice(get_string('guestsnotallowed', '', $course->fullname), "$CFG->wwwroot/login/index.php");
1641                     } else {
1642                         notify(get_string('guestsnotallowed', '', $course->fullname));
1643                         echo '<div class="notifyproblem">'.switchroles_form($course->id).'</div>';
1644                         print_footer($course);
1645                         exit;
1646                     }
1647                     break;
1648             }
1650     /// For non-guests, check if they have course view access
1652         } else if (has_capability('moodle/course:view', $context)) {
1653             if (!empty($USER->realuser)) {   // Make sure the REAL person can also access this course
1654                 if (!has_capability('moodle/course:view', $context, $USER->realuser)) {
1655                     print_header_simple();
1656                     notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
1657                 }
1658             }
1660         /// Make sure they can read this activity too, if specified
1662             if (!empty($cm) and !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) { 
1663                 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1664             }
1665             return;   // User is allowed to see this course
1667     /// Otherwise, for non-guests who don't currently have access, check if they can be allowed in as a guest
1669         } else {
1671             if ($course->guest == 1) {    // Temporarily assign them guest role for this context
1672                  if (!load_guest_role($context)) {
1673                     print_header_simple();
1674                     notice(get_string('guestsnotallowed', '', $course->fullname), "$CFG->wwwroot/login/index.php");
1675                  }
1676                  return;
1677             }
1678         }
1681     /// Currently not enrolled in the course, so see if they want to enrol
1682         $SESSION->wantsurl = $FULLME;
1683         redirect($CFG->wwwroot .'/course/enrol.php?id='. $courseid);
1684         die;
1685     }
1690 /**
1691  * This function just makes sure a user is logged out.
1692  *
1693  * @uses $CFG
1694  * @uses $USER
1695  */
1696 function require_logout() {
1698     global $USER, $CFG;
1700     if (isset($USER) and isset($USER->id)) {
1701         add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
1703         if ($USER->auth == 'cas' && !empty($CFG->cas_enabled)) {
1704             require($CFG->dirroot.'/auth/cas/logout.php');
1705         }
1706     }
1708     if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
1709         // This method is just to try to avoid silly warnings from PHP 4.3.0
1710         session_unregister("USER");
1711         session_unregister("SESSION");
1712     }
1714     setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath);
1715     unset($_SESSION['USER']);
1716     unset($_SESSION['SESSION']);
1718     unset($SESSION);
1719     unset($USER);
1723 /**
1724  * This is a weaker version of {@link require_login()} which only requires login
1725  * when called from within a course rather than the site page, unless
1726  * the forcelogin option is turned on.
1727  *
1728  * @uses $CFG
1729  * @param object $course The course object in question
1730  * @param bool $autologinguest Allow autologin guests if that is wanted
1731  * @param object $cm Course activity module if known
1732  */
1733 function require_course_login($course, $autologinguest=true, $cm=null) {
1734     global $CFG;
1735     if (!empty($CFG->forcelogin)) {
1736         require_login();
1737     }
1738     if ($course->id != SITEID) {
1739         require_login($course->id, $autologinguest, $cm);
1740     }
1743 /**
1744  * Modify the user table by setting the currently logged in user's
1745  * last login to now.
1746  *
1747  * @uses $USER
1748  * @return bool
1749  */
1750 function update_user_login_times() {
1751     global $USER;
1753     $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
1754     $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
1756     $user->id = $USER->id;
1758     return update_record('user', $user);
1761 /**
1762  * Determines if a user has completed setting up their account.
1763  *
1764  * @param user $user A {@link $USER} object to test for the existance of a valid name and email
1765  * @return bool
1766  */
1767 function user_not_fully_set_up($user) {
1768     return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
1771 function over_bounce_threshold($user) {
1773     global $CFG;
1775     if (empty($CFG->handlebounces)) {
1776         return false;
1777     }
1778     // set sensible defaults
1779     if (empty($CFG->minbounces)) {
1780         $CFG->minbounces = 10;
1781     }
1782     if (empty($CFG->bounceratio)) {
1783         $CFG->bounceratio = .20;
1784     }
1785     $bouncecount = 0;
1786     $sendcount = 0;
1787     if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1788         $bouncecount = $bounce->value;
1789     }
1790     if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1791         $sendcount = $send->value;
1792     }
1793     return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
1796 /**
1797  * @param $user - object containing an id
1798  * @param $reset - will reset the count to 0
1799  */
1800 function set_send_count($user,$reset=false) {
1801     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1802         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1803         update_record('user_preferences',$pref);
1804     }
1805     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1806         // make a new one
1807         $pref->name = 'email_send_count';
1808         $pref->value = 1;
1809         $pref->userid = $user->id;
1810         insert_record('user_preferences',$pref, false);
1811     }
1814 /**
1815 * @param $user - object containing an id
1816  * @param $reset - will reset the count to 0
1817  */
1818 function set_bounce_count($user,$reset=false) {
1819     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1820         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1821         update_record('user_preferences',$pref);
1822     }
1823     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1824         // make a new one
1825         $pref->name = 'email_bounce_count';
1826         $pref->value = 1;
1827         $pref->userid = $user->id;
1828         insert_record('user_preferences',$pref, false);
1829     }
1832 /**
1833  * Keeps track of login attempts
1834  *
1835  * @uses $SESSION
1836  */
1837 function update_login_count() {
1839     global $SESSION;
1841     $max_logins = 10;
1843     if (empty($SESSION->logincount)) {
1844         $SESSION->logincount = 1;
1845     } else {
1846         $SESSION->logincount++;
1847     }
1849     if ($SESSION->logincount > $max_logins) {
1850         unset($SESSION->wantsurl);
1851         error(get_string('errortoomanylogins'));
1852     }
1855 /**
1856  * Resets login attempts
1857  *
1858  * @uses $SESSION
1859  */
1860 function reset_login_count() {
1861     global $SESSION;
1863     $SESSION->logincount = 0;
1866 function sync_metacourses() {
1868     global $CFG;
1870     if (!$courses = get_records('course', 'metacourse', 1)) {
1871         return;
1872     }
1874     foreach ($courses as $course) {
1875         sync_metacourse($course);
1876     }
1879 /**
1880  * Goes through all enrolment records for the courses inside the metacourse and sync with them.
1881  */
1883 function sync_metacourse($course) {
1884     global $CFG;
1885     $status = true;
1887     if (!is_object($course)) {
1888         if (!$course = get_record('course', 'id', $course)) {
1889             return false; // invalid course id
1890         }
1891     }
1892     
1893     if (empty($course->metacourse)) {
1894         return false; // can not sync normal course or not $course object
1895     }
1897     $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
1899     if (!$roles = get_records('role')) {
1900         return false; // hmm, there should be always at least one role
1901     }
1902     
1903     // always keep metacourse managers
1904     if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
1905         $managers = array_keys($users);
1906     } else {
1907         $managers = array();
1908     }
1910     // find all role users in child courses + build list of current metacourse assignments
1911     $in_childs = array(); 
1912     foreach ($roles as $role) {
1913         if ($users = get_role_users($role->id, $context, false)) {
1914             $current[$role->id] = array_keys($users);
1915         } else {
1916             $current[$role->id] = array();
1917         }
1918         $in_childs[$role->id] = array(); //initialze array
1919     }
1920     if ($children = get_records('course_meta', 'parent_course', $course->id)) {
1921         foreach ($children as $child) {
1922             $ch_context = get_context_instance(CONTEXT_COURSE, $child->child_course);
1923             foreach ($roles as $role) {
1924                 if ($users = get_role_users($role->id, $ch_context, false)) {
1926                     $users = array_keys($users);
1927                     $in_childs[$role->id] = array_merge($in_childs[$role->id], $users);
1928                 }
1929             }
1930         }
1931     }
1932     
1933     foreach ($roles as $role) {
1934         //clean up the duplicates from cuncurrent assignments in child courses
1935         $in_childs[$role->id] = array_unique($in_childs[$role->id]);
1936         //make a list of all potentially affected users
1937     }
1939     foreach ($roles as $role) {
1940         foreach ($current[$role->id] as $userid) {
1941             if (in_array($userid, $in_childs[$role->id])) {
1942                 // ok - no need to change anything
1943                 unset($in_childs[$role->id][array_search($userid, $in_childs[$role->id])]);
1944             } else {
1945                 // unassign if not metacourse manager
1946                 if (!in_array($userid, $managers)) {
1947                     //echo "unassigning uid: $userid from role: $role->id in context: $context->id <br>\n";
1948                     role_unassign($role->id, $userid, 0, $context->id);
1949                 }
1950             }
1951         }
1952         // now assign roles to those left in $in_childs  
1953         foreach ($in_childs[$role->id] as $userid) {
1954             //echo "  assigning uid: $userid from role: $role->id in context: $context->id <br>\n";
1955             role_assign($role->id, $userid, 0, $context->id);
1956         }        
1957     }
1959 // TODO: finish timeend and timestart
1960 // maybe we could rely on cron job to do the cleaning from time to time
1961     return true;
1964 /**
1965  * Adds a record to the metacourse table and calls sync_metacoures
1966  */
1967 function add_to_metacourse ($metacourseid, $courseid) {
1969     if (!$metacourse = get_record("course","id",$metacourseid)) {
1970         return false;
1971     }
1973     if (!$course = get_record("course","id",$courseid)) {
1974         return false;
1975     }
1977     if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
1978         $rec->parent_course = $metacourseid;
1979         $rec->child_course = $courseid;
1980         if (!insert_record('course_meta',$rec)) {
1981             return false;
1982         }
1983         return sync_metacourse($metacourseid);
1984     }
1985     return true;
1989 /**
1990  * Removes the record from the metacourse table and calls sync_metacourse
1991  */
1992 function remove_from_metacourse($metacourseid, $courseid) {
1994     if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
1995         return sync_metacourse($metacourseid);
1996     }
1997     return false;
2001 /**
2002  * Determines if a user is currently logged in
2003  *
2004  * @uses $USER
2005  * @return bool
2006  */
2007 function isloggedin() {
2008     global $USER;
2010     return (!empty($USER->id));
2014 /**
2015  * Determines if the currently logged in user is in editing mode
2016  *
2017  * @uses $USER
2018  * @param int $courseid The id of the course being tested
2019  * @param user $user A {@link $USER} object. If null then the currently logged in user is used.
2020  * @return bool
2021  */
2022 function isediting($courseid, $user=NULL) {
2023     global $USER;
2024     if (!$user) {
2025         $user = $USER;
2026     }
2027     if (empty($user->editing)) {
2028         return false;
2029     }
2030     return ($user->editing and has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $courseid)));
2033 /**
2034  * Determines if the logged in user is currently moving an activity
2035  *
2036  * @uses $USER
2037  * @param int $courseid The id of the course being tested
2038  * @return bool
2039  */
2040 function ismoving($courseid) {
2041     global $USER;
2043     if (!empty($USER->activitycopy)) {
2044         return ($USER->activitycopycourse == $courseid);
2045     }
2046     return false;
2049 /**
2050  * Given an object containing firstname and lastname
2051  * values, this function returns a string with the
2052  * full name of the person.
2053  * The result may depend on system settings
2054  * or language.  'override' will force both names
2055  * to be used even if system settings specify one.
2056  *
2057  * @uses $CFG
2058  * @uses $SESSION
2059  * @param object $user A {@link $USER} object to get full name of
2060  * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2061  */
2062 function fullname($user, $override=false) {
2064     global $CFG, $SESSION;
2066     if (!isset($user->firstname) and !isset($user->lastname)) {
2067         return '';
2068     }
2070     if (!$override) {
2071         if (!empty($CFG->forcefirstname)) {
2072             $user->firstname = $CFG->forcefirstname;
2073         }
2074         if (!empty($CFG->forcelastname)) {
2075             $user->lastname = $CFG->forcelastname;
2076         }
2077     }
2079     if (!empty($SESSION->fullnamedisplay)) {
2080         $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2081     }
2083     if ($CFG->fullnamedisplay == 'firstname lastname') {
2084         return $user->firstname .' '. $user->lastname;
2086     } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2087         return $user->lastname .' '. $user->firstname;
2089     } else if ($CFG->fullnamedisplay == 'firstname') {
2090         if ($override) {
2091             return get_string('fullnamedisplay', '', $user);
2092         } else {
2093             return $user->firstname;
2094         }
2095     }
2097     return get_string('fullnamedisplay', '', $user);
2100 /**
2101  * Sets a moodle cookie with an encrypted string
2102  *
2103  * @uses $CFG
2104  * @uses DAYSECS
2105  * @uses HOURSECS
2106  * @param string $thing The string to encrypt and place in a cookie
2107  */
2108 function set_moodle_cookie($thing) {
2109     global $CFG;
2111     if ($thing == 'guest') {  // Ignore guest account
2112         return;
2113     }
2115     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2117     $days = 60;
2118     $seconds = DAYSECS*$days;
2120     setCookie($cookiename, '', time() - HOURSECS, '/');
2121     setCookie($cookiename, rc4encrypt($thing), time()+$seconds, '/');
2124 /**
2125  * Gets a moodle cookie with an encrypted string
2126  *
2127  * @uses $CFG
2128  * @return string
2129  */
2130 function get_moodle_cookie() {
2131     global $CFG;
2133     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2135     if (empty($_COOKIE[$cookiename])) {
2136         return '';
2137     } else {
2138         $thing = rc4decrypt($_COOKIE[$cookiename]);
2139         return ($thing == 'guest') ? '': $thing;  // Ignore guest account
2140     }
2143 /**
2144  * Returns true if an internal authentication method is being used.
2145  * if method not specified then, global default is assumed
2146  *
2147  * @uses $CFG
2148  * @param string $auth Form of authentication required
2149  * @return bool
2150  * @todo Outline auth types and provide code example
2151  */
2152 function is_internal_auth($auth='') {
2153 /// Returns true if an internal authentication method is being used.
2154 /// If auth not specified then global default is assumed
2156     global $CFG;
2158     if (empty($auth)) {
2159         $auth = $CFG->auth;
2160     }
2162     return ($auth == "email" || $auth == "none" || $auth == "manual");
2165 /**
2166  * Returns an array of user fields
2167  *
2168  * @uses $CFG
2169  * @uses $db
2170  * @return array User field/column names
2171  */
2172 function get_user_fieldnames() {
2174     global $CFG, $db;
2176     $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2177     unset($fieldarray['ID']);
2179     return $fieldarray;
2182 /**
2183  * Creates a bare-bones user record
2184  *
2185  * @uses $CFG
2186  * @param string $username New user's username to add to record
2187  * @param string $password New user's password to add to record
2188  * @param string $auth Form of authentication required
2189  * @return object A {@link $USER} object
2190  * @todo Outline auth types and provide code example
2191  */
2192 function create_user_record($username, $password, $auth='') {
2193     global $CFG;
2195     //just in case check text case
2196     $username = trim(moodle_strtolower($username));
2198     if (function_exists('auth_get_userinfo')) {
2199         if ($newinfo = auth_get_userinfo($username)) {
2200             $newinfo = truncate_userinfo($newinfo);
2201             foreach ($newinfo as $key => $value){
2202                 $newuser->$key = addslashes(stripslashes($value)); // Just in case
2203             }
2204         }
2205     }
2207     if (!empty($newuser->email)) {
2208         if (email_is_not_allowed($newuser->email)) {
2209             unset($newuser->email);
2210         }
2211     }
2213     $newuser->auth = (empty($auth)) ? $CFG->auth : $auth;
2214     $newuser->username = $username;
2215     update_internal_user_password($newuser, $password, false);
2216     $newuser->lang = $CFG->lang;
2217     $newuser->confirmed = 1;
2218     $newuser->lastip = getremoteaddr();
2219     $newuser->timemodified = time();
2221     if (insert_record('user', $newuser)) {
2222          $user = get_complete_user_data('username', $newuser->username);
2223          if($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'}){
2224              set_user_preference('auth_forcepasswordchange', 1, $user->id);
2225          }
2226          return $user;
2227     }
2228     return false;
2231 /**
2232  * Will update a local user record from an external source
2233  *
2234  * @uses $CFG
2235  * @param string $username New user's username to add to record
2236  * @return user A {@link $USER} object
2237  */
2238 function update_user_record($username) {
2239     global $CFG;
2241     if (function_exists('auth_get_userinfo')) {
2242         $username = trim(moodle_strtolower($username)); /// just in case check text case
2244         $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2245         $authconfig = get_config('auth/' . $oldinfo->auth);
2247         if ($newinfo = auth_get_userinfo($username)) {
2248             $newinfo = truncate_userinfo($newinfo);
2249             foreach ($newinfo as $key => $value){
2250                 $confkey = 'field_updatelocal_' . $key;
2251                 if (!empty($authconfig->$confkey) && $authconfig->$confkey === 'onlogin') {
2252                     $value = addslashes(stripslashes($value));   // Just in case
2253                     set_field('user', $key, $value, 'username', $username)
2254                         || error_log("Error updating $key for $username");
2255                 }
2256             }
2257         }
2258     }
2259     return get_complete_user_data('username', $username);
2262 function truncate_userinfo($info) {
2263 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2264 /// which may have large fields
2266     // define the limits
2267     $limit = array(
2268                     'username'    => 100,
2269                     'idnumber'    =>  64,
2270                     'firstname'   => 100,
2271                     'lastname'    => 100,
2272                     'email'       => 100,
2273                     'icq'         =>  15,
2274                     'phone1'      =>  20,
2275                     'phone2'      =>  20,
2276                     'institution' =>  40,
2277                     'department'  =>  30,
2278                     'address'     =>  70,
2279                     'city'        =>  20,
2280                     'country'     =>   2,
2281                     'url'         => 255,
2282                     );
2284     // apply where needed
2285     foreach (array_keys($info) as $key) {
2286         if (!empty($limit[$key])) {
2287             $info[$key] = trim(substr($info[$key],0, $limit[$key]));
2288         }
2289     }
2291     return $info;
2294 /**
2295  * Retrieve the guest user object
2296  *
2297  * @uses $CFG
2298  * @return user A {@link $USER} object
2299  */
2300 function guest_user() {
2301     global $CFG;
2303     if ($newuser = get_record('user', 'username', 'guest')) {
2304         $newuser->loggedin = true;
2305         $newuser->confirmed = 1;
2306         $newuser->site = $CFG->wwwroot;
2307         $newuser->lang = $CFG->lang;
2308         $newuser->lastip = getremoteaddr();
2309     }
2311     return $newuser;
2314 /**
2315  * Given a username and password, this function looks them
2316  * up using the currently selected authentication mechanism,
2317  * and if the authentication is successful, it returns a
2318  * valid $user object from the 'user' table.
2319  *
2320  * Uses auth_ functions from the currently active auth module
2321  *
2322  * @uses $CFG
2323  * @param string $username  User's username
2324  * @param string $password  User's password
2325  * @return user|flase A {@link $USER} object or false if error
2326  */
2327 function authenticate_user_login($username, $password) {
2329     global $CFG;
2331     // First try to find the user in the database
2333     if (!$user = get_complete_user_data('username', $username)) {
2334         $user->id = 0;     // Not a user
2335         $user->auth = $CFG->auth;
2336     }
2338     // Sort out the authentication method we are using.
2340     if (empty($CFG->auth)) {
2341         $CFG->auth = 'manual';     // Default authentication module
2342     }
2344     if (empty($user->auth)) {      // For some reason it isn't set yet
2345         if (!empty($user->id) && (isadmin($user->id) || isguest($user->id))) {
2346             $auth = 'manual';    // Always assume these guys are internal
2347         } else {
2348             $auth = $CFG->auth;  // Normal users default to site method
2349         }
2350         // update user record from external DB
2351         if ($user->auth != 'manual' && $user->auth != 'email') {
2352             $user = update_user_record($username);
2353         }
2354     } else {
2355         $auth = $user->auth;
2356     }
2358     if (detect_munged_arguments($auth, 0)) {   // For safety on the next require
2359         return false;
2360     }
2362     if (!file_exists($CFG->dirroot .'/auth/'. $auth .'/lib.php')) {
2363         $auth = 'manual';    // Can't find auth module, default to internal
2364     }
2366     require_once($CFG->dirroot .'/auth/'. $auth .'/lib.php');
2368     if (auth_user_login($username, $password)) {  // Successful authentication
2369         if ($user->id) {                          // User already exists in database
2370             if (empty($user->auth)) {             // For some reason auth isn't set yet
2371                 set_field('user', 'auth', $auth, 'username', $username);
2372             }
2373             update_internal_user_password($user, $password);
2374             if (!is_internal_auth()) {            // update user record from external DB
2375                 $user = update_user_record($username);
2376             }
2377         } else {
2378             $user = create_user_record($username, $password, $auth);
2379         }
2381         if (function_exists('auth_iscreator')) {    // Check if the user is a creator
2382             $useriscreator = auth_iscreator($username);
2383             if (!is_null($useriscreator)) {
2384                 if ($useriscreator) {
2385                     if (! record_exists('user_coursecreators', 'userid', $user->id)) {
2386                         $cdata->userid = $user->id;
2387                         if (! insert_record('user_coursecreators', $cdata)) {
2388                             error('Cannot add user to course creators.');
2389                         }
2390                     }
2391                 } else {
2392                     if (record_exists('user_coursecreators', 'userid', $user->id)) {
2393                         if (! delete_records('user_coursecreators', 'userid', $user->id)) {
2394                             error('Cannot remove user from course creators.');
2395                         }
2396                     }
2397                 }
2398             }
2399         }
2401     /// Log in to a second system if necessary
2402         if (!empty($CFG->sso)) {
2403             include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
2404             if (function_exists('sso_user_login')) {
2405                 if (!sso_user_login($username, $password)) {   // Perform the signon process
2406                     notify('Second sign-on failed');
2407                 }
2408             }
2409         }
2411         return $user;
2413     } else {
2414         add_to_log(0, 'login', 'error', 'index.php', $username);
2415         error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Failed Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
2416         return false;
2417     }
2420 /**
2421  * Compare password against hash stored in internal user table.
2422  * If necessary it also updates the stored hash to new format.
2423  * 
2424  * @param object user
2425  * @param string plain text password
2426  * @return bool is password valid?
2427  */
2428 function validate_internal_user_password(&$user, $password) {
2429     global $CFG;
2431     if (!isset($CFG->passwordsaltmain)) {
2432         $CFG->passwordsaltmain = '';
2433     }
2435     $validated = false;
2437     if (!empty($CFG->unicodedb)) {
2438         $textlib = textlib_get_instance();
2439         $convpassword = $textlib->convert($password, 'UTF-8', get_string('oldcharset'));
2440     } else {
2441         $convpassword = $password; //no conversion yet
2442     }
2444     if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
2445         or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
2446         $validated = true;
2447     } else {
2448         for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
2449             $alt = 'passwordsaltalt'.$i;
2450             if (!empty($CFG->$alt)) {
2451                 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
2452                     $validated = true;
2453                     break;
2454                 }
2455             }
2456         }
2457     }
2459     if ($validated) {
2460         // force update of password hash using latest main password salt and encoding if needed
2461         update_internal_user_password($user, $password);
2462     }
2464     return $validated;
2467 /**
2468  * Calculate hashed value from password using current hash mechanism.
2469  * 
2470  * @param string password
2471  * @return string password hash
2472  */
2473 function hash_internal_user_password($password) {
2474     global $CFG;
2476     if (isset($CFG->passwordsaltmain)) {
2477         return md5($password.$CFG->passwordsaltmain);
2478     } else {
2479         return md5($password);
2480     }
2483 /**
2484  * Update pssword hash in user object.
2485  * 
2486  * @param object user
2487  * @param string plain text password
2488  * @param bool store changes also in db, default true
2489  * @return true if hash changed
2490  */
2491 function update_internal_user_password(&$user, $password, $storeindb=true) {
2492     global $CFG;
2494     if (!empty($CFG->{$user->auth.'_preventpassindb'})) {
2495         $hashedpassword = 'not cached';
2496     } else {
2497         $hashedpassword = hash_internal_user_password($password);
2498     }
2500     if ($user->password != $hashedpassword) {
2501         if ($storeindb) {
2502             if (!set_field('user', 'password',  $hashedpassword, 'username', $user->username)) {
2503                 return false;
2504             }
2505         }
2506         $user->password = $hashedpassword;
2507     }
2508     return true;
2511 /**
2512  * Get a complete user record, which includes all the info
2513  * in the user record
2514  * Intended for setting as $USER session variable
2515  *
2516  * @uses $CFG
2517  * @uses SITEID
2518  * @param string $field The user field to be checked for a given value.
2519  * @param string $value The value to match for $field.
2520  * @return user A {@link $USER} object.
2521  */
2522 function get_complete_user_data($field, $value) {
2524     global $CFG;
2526     if (!$field || !$value) {
2527         return false;
2528     }
2530 /// Get all the basic user data
2532     if (! $user = get_record_select('user', $field .' = \''. $value .'\' AND deleted <> \'1\'')) {
2533         return false;
2534     }
2536 /// Get various settings and preferences
2538     if ($displays = get_records('course_display', 'userid', $user->id)) {
2539         foreach ($displays as $display) {
2540             $user->display[$display->course] = $display->display;
2541         }
2542     }
2544     if ($preferences = get_records('user_preferences', 'userid', $user->id)) {
2545         foreach ($preferences as $preference) {
2546             $user->preference[$preference->name] = $preference->value;
2547         }
2548     }
2550     if ($groups = get_records('groups_members', 'userid', $user->id)) {
2551         foreach ($groups as $groupmember) {
2552             $courseid = get_field('groups', 'courseid', 'id', $groupmember->groupid);
2553             //change this to 2D array so we can put multiple groups in a course
2554             $user->groupmember[$courseid][] = $groupmember->groupid;
2555         }
2556     }
2558 /// Rewrite some variables if necessary
2559     if (!empty($user->description)) {
2560         $user->description = true;   // No need to cart all of it around
2561     }
2562     if ($user->username == 'guest') {
2563         $user->lang       = $CFG->lang;               // Guest language always same as site
2564         $user->firstname  = get_string('guestuser');  // Name always in current language
2565         $user->lastname   = ' ';
2566     }
2568     $user->loggedin = true;
2569     $user->site     = $CFG->wwwroot; // for added security, store the site in the session
2570     $user->sesskey  = random_string(10);
2571     $user->sessionIP = md5(getremoteaddr());   // Store the current IP in the session
2573     return $user;
2578 /*
2579  * When logging in, this function is run to set certain preferences
2580  * for the current SESSION
2581  */
2582 function set_login_session_preferences() {
2583     global $SESSION, $CFG;
2585     $SESSION->justloggedin = true;
2587     unset($SESSION->lang);
2589     // Restore the calendar filters, if saved
2590     if (intval(get_user_preferences('calendar_persistflt', 0))) {
2591         include_once($CFG->dirroot.'/calendar/lib.php');
2592         calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
2593     }
2597 /**
2598  * Delete a course, including all related data from the database,
2599  * and any associated files from the moodledata folder.
2600  *
2601  * @param int $courseid The id of the course to delete.
2602  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2603  * @return bool true if all the removals succeeded. false if there were any failures. If this
2604  *             method returns false, some of the removals will probably have succeeded, and others
2605  *             failed, but you have no way of knowing which.
2606  */
2607 function delete_course($courseid, $showfeedback = true) {
2608     global $CFG;
2609     $result = true;
2611     if (!remove_course_contents($courseid, $showfeedback)) {
2612         if ($showfeedback) {
2613             notify("An error occurred while deleting some of the course contents.");
2614         }
2615         $result = false;
2616     }
2618     if (!delete_records("course", "id", $courseid)) {
2619         if ($showfeedback) {
2620             notify("An error occurred while deleting the main course record.");
2621         }
2622         $result = false;
2623     }
2625     if (!delete_records('context', 'contextlevel', CONTEXT_COURSE, 'instanceid', $courseid)) {
2626         if ($showfeedback) {
2627             notify("An error occurred while deleting the main context record.");
2628         }
2629         $result = false;
2630     }
2632     if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
2633         if ($showfeedback) {
2634             notify("An error occurred while deleting the course files.");
2635         }
2636         $result = false;
2637     }
2639     return $result;
2642 /**
2643  * Clear a course out completely, deleting all content
2644  * but don't delete the course itself
2645  *
2646  * @uses $CFG
2647  * @param int $courseid The id of the course that is being deleted
2648  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2649  * @return bool true if all the removals succeeded. false if there were any failures. If this
2650  *             method returns false, some of the removals will probably have succeeded, and others
2651  *             failed, but you have no way of knowing which.
2652  */
2653 function remove_course_contents($courseid, $showfeedback=true) {
2655     global $CFG;
2657     $result = true;
2659     if (! $course = get_record('course', 'id', $courseid)) {
2660         error('Course ID was incorrect (can\'t find it)');
2661     }
2663     $strdeleted = get_string('deleted');
2665 /// First delete every instance of every module
2667     if ($allmods = get_records('modules') ) {
2668         foreach ($allmods as $mod) {
2669             $modname = $mod->name;
2670             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
2671             $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance
2672             $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon)
2673             $count=0;
2674             if (file_exists($modfile)) {
2675                 include_once($modfile);
2676                 if (function_exists($moddelete)) {
2677                     if ($instances = get_records($modname, 'course', $course->id)) {
2678                         foreach ($instances as $instance) {
2679                             if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
2680                                 delete_context(CONTEXT_MODULE, $cm->id);
2681                             }
2682                             if ($moddelete($instance->id)) {
2683                                 $count++;
2685                             } else {
2686                                 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
2687                                 $result = false;
2688                             }
2689                         }
2690                     }
2691                 } else {
2692                     notify('Function '. $moddelete() .'doesn\'t exist!');
2693                     $result = false;
2694                 }
2696                 if (function_exists($moddeletecourse)) {
2697                     $moddeletecourse($course, $showfeedback);
2698                 }
2699             }
2700             if ($showfeedback) {
2701                 notify($strdeleted .' '. $count .' x '. $modname);
2702             }
2703         }
2704     } else {
2705         error('No modules are installed!');
2706     }
2708 /// Give local code a chance to delete its references to this course.
2709     require_once('locallib.php');
2710     notify_local_delete_course($courseid, $showfeedback);
2712 /// Delete course blocks
2714     if ($blocks = get_records_sql("SELECT * 
2715                                    FROM {$CFG->prefix}block_instance
2716                                    WHERE pagetype = '".PAGE_COURSE_VIEW."'
2717                                    AND pageid = $course->id")) {
2718         if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
2719             if ($showfeedback) {
2720                 notify($strdeleted .' block_instance');
2721             }
2722             foreach ($blocks as $block) {  /// Delete any associated contexts for this block
2723                 delete_context(CONTEXT_BLOCK, $block->id);
2724             }
2725         } else {
2726             $result = false;
2727         }
2728     }
2730 /// Delete any groups
2731     if ($groups = get_records('groups', 'courseid', $course->id)) {
2732         if (delete_records('groups', 'courseid', $course->id)) {
2733             if ($showfeedback) {
2734                 notify($strdeleted .' groups');
2735             }
2736             foreach ($groups as $group) {
2737                 if (delete_records('groups_members', 'groupid', $group->id)) {
2738                     if ($showfeedback) {
2739                         notify($strdeleted .' groups_members');
2740                     }
2741                 } else {
2742                     $result = false;
2743                 }
2744                 /// Delete any associated context for this group
2745                 delete_context(CONTEXT_GROUP, $group->id);
2746             }
2747         } else {
2748             $result = false;
2749         }
2750     }
2752 /// Delete all related records in other tables that may have a courseid
2753 /// This array stores the tables that need to be cleared, as
2754 /// table_name => column_name that contains the course id.
2756     $tablestoclear = array(
2757         'event' => 'courseid', // Delete events
2758         'log' => 'course', // Delete logs
2759         'course_sections' => 'course', // Delete any course stuff
2760         'course_modules' => 'course',
2761         'grade_category' => 'courseid', // Delete gradebook stuff
2762         'grade_exceptions' => 'courseid',
2763         'grade_item' => 'courseid',
2764         'grade_letter' => 'courseid',
2765         'grade_preferences' => 'courseid'
2766     );
2767     foreach ($tablestoclear as $table => $col) {
2768         if (delete_records($table, $col, $course->id)) {
2769             if ($showfeedback) {
2770                 notify($strdeleted . ' ' . $table);
2771             }
2772         } else {
2773             $result = false;
2774         }
2775     }
2778 /// Clean up metacourse stuff
2780     if ($course->metacourse) {
2781         delete_records("course_meta","parent_course",$course->id);
2782         sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
2783         if ($showfeedback) {
2784             notify("$strdeleted course_meta");
2785         }
2786     } else {
2787         if ($parents = get_records("course_meta","child_course",$course->id)) {
2788             foreach ($parents as $parent) {
2789                 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
2790             }
2791             if ($showfeedback) {
2792                 notify("$strdeleted course_meta");
2793             }
2794         }
2795     }
2797 /// Delete questions and question categories
2798     include_once($CFG->libdir.'/questionlib.php');
2799     question_delete_course($course, $showfeedback);
2801 /// Delete all roles and overiddes in the course context (but keep the course context)
2802     delete_context(CONTEXT_COURSE, $course->id);
2804     return $result;
2808 /**
2809  * This function will empty a course of USER data as much as
2810 /// possible. It will retain the activities and the structure
2811 /// of the course.
2812  *
2813  * @uses $USER
2814  * @uses $SESSION
2815  * @uses $CFG
2816  * @param object $data an object containing all the boolean settings and courseid
2817  * @param bool $showfeedback  if false then do it all silently
2818  * @return bool
2819  * @todo Finish documenting this function
2820  */
2821 function reset_course_userdata($data, $showfeedback=true) {
2823     global $CFG, $USER, $SESSION;
2825     $result = true;
2827     $strdeleted = get_string('deleted');
2829     // Look in every instance of every module for data to delete
2831     if ($allmods = get_records('modules') ) {
2832         foreach ($allmods as $mod) {
2833             $modname = $mod->name;
2834             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
2835             $moddeleteuserdata = $modname .'_delete_userdata';   // Function to delete user data
2836             if (file_exists($modfile)) {
2837                 @include_once($modfile);
2838                 if (function_exists($moddeleteuserdata)) {
2839                     $moddeleteuserdata($data, $showfeedback);
2840                 }
2841             }
2842         }
2843     } else {
2844         error('No modules are installed!');
2845     }
2847     // Delete other stuff
2849     if (!empty($data->reset_students)) {
2850         /// Delete student enrolments
2851         if (delete_records('user_students', 'course', $data->courseid)) {
2852             if ($showfeedback) {
2853                 notify($strdeleted .' user_students', 'notifysuccess');
2854             }
2855         } else {
2856             $result = false;
2857         }
2859         /// Delete group members (but keep the groups)
2860         if ($groups = get_records('groups', 'courseid', $data->courseid)) {
2861             foreach ($groups as $group) {
2862                 if (delete_records('groups_members', 'groupid', $group->id)) {
2863                     if ($showfeedback) {
2864                         notify($strdeleted .' groups_members', 'notifysuccess');
2865                     }
2866                 } else {
2867                     $result = false;
2868                 }
2869             }
2870         }
2871     }
2873     if (!empty($data->reset_teachers)) {
2874         if (delete_records('user_teachers', 'course', $data->courseid)) {
2875             if ($showfeedback) {
2876                 notify($strdeleted .' user_teachers', 'notifysuccess');
2877             }
2878         } else {
2879             $result = false;
2880         }
2881     }
2883     if (!empty($data->reset_groups)) {
2884         if ($groups = get_records('groups', 'courseid', $data->courseid)) {
2885             foreach ($groups as $group) {
2886                 if (delete_records('groups', 'id', $group->id)) {
2887                     if ($showfeedback) {
2888                         notify($strdeleted .' groups', 'notifysuccess');
2889                     }
2890                 } else {
2891                     $result = false;
2892                 }
2893             }
2894         }
2895     }
2897     if (!empty($data->reset_events)) {
2898         if (delete_records('event', 'courseid', $data->courseid)) {
2899             if ($showfeedback) {
2900                 notify($strdeleted .' event', 'notifysuccess');
2901             }
2902         } else {
2903             $result = false;
2904         }
2905     }
2907     if (!empty($data->reset_logs)) {
2908         if (delete_records('log', 'course', $data->courseid)) {
2909             if ($showfeedback) {
2910                 notify($strdeleted .' log', 'notifysuccess');
2911             }
2912         } else {
2913             $result = false;
2914         }
2915     }
2917     // deletes all role assignments, and local override, these have no courseid in table and needs separate process
2918     $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
2919     delete_records('role_assignments', 'contextid', $context->id);
2920     delete_records('role_role_capabilities', 'contextid', $context->id);
2921    
2922     return $result;
2926 /// GROUPS /////////////////////////////////////////////////////////
2929 /**
2930  * Determines if the user a member of the given group
2931  *
2932  * @uses $USER
2933  * @param int $groupid The group to check the membership of
2934  * @param int $userid The user to check against the group
2935  * @return bool
2936  */
2937 function ismember($groupid, $userid=0) {
2938     global $USER;
2940     if (!$groupid) {   // No point doing further checks
2941         return false;
2942     }
2943     //if groupid is supplied in array format
2944     if (!$userid) {
2945         if (empty($USER->groupmember)) {
2946             return false;
2947         }
2948         //changed too for multiple groups
2949         foreach ($USER->groupmember as $courseid => $mgroupid) {
2950             //need to loop one more time...
2951             if (is_array($mgroupid)) {
2952                 foreach ($mgroupid as $index => $mygroupid) {
2953                     if ($mygroupid == $groupid) {
2954                         return true;
2955                     }
2956                 }
2957             } else if ($mgroupid == $groupid) {
2958                 return true;
2959             }
2960         }
2961         return false;
2962     }
2964     if (is_array($groupid)){
2965         foreach ($groupid as $index => $val){
2966             if (record_exists('groups_members', 'groupid', $val, 'userid', $userid)){
2967                 return true;
2968             }
2969         }
2970     }
2971     else {
2972         return record_exists('groups_members', 'groupid', $groupid, 'userid', $userid);
2973     }
2974     return false;
2976     //else group id is in single format
2978     //return record_exists('groups_members', 'groupid', $groupid, 'userid', $userid);
2981 /**
2982  * Add a user to a group, return true upon success or if user already a group member
2983  *
2984  * @param int $groupid  The group id to add user to
2985  * @param int $userid   The user id to add to the group
2986  * @return bool
2987  */
2988 function add_user_to_group ($groupid, $userid) {
2989     if (ismember($groupid, $userid)) return true;
2990     $record->groupid = $groupid;
2991     $record->userid = $userid;
2992     $record->timeadded = time();
2993     return (insert_record('groups_members', $record) !== false);
2997 /**
2998  * Get the group ID of the current user in the given course
2999  *
3000  * @uses $USER
3001  * @param int $courseid The course being examined - relates to id field in 'course' table.
3002  * @return int
3003  */
3004 function mygroupid($courseid) {
3005     global $USER;
3006     if (empty($USER->groupmember[$courseid])) {
3007         return 0;
3008     } else {
3009         //this is an array of ids >.<
3010         return $USER->groupmember[$courseid];
3011     }
3014 /**
3015  * For a given course, and possibly course module, determine
3016  * what the current default groupmode is:
3017  * NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3018  *
3019  * @param course $course A {@link $COURSE} object
3020  * @param object $cm A course module object
3021  * @return int A group mode (NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS)
3022  */
3023 function groupmode($course, $cm=null) {
3025     if ($cm and !$course->groupmodeforce) {
3026         return $cm->groupmode;
3027     }
3028     return $course->groupmode;
3032 /**
3033  * Sets the current group in the session variable
3034  *
3035  * @uses $SESSION
3036  * @param int $courseid The course being examined - relates to id field in 'course' table.
3037  * @param int $groupid The group being examined.
3038  * @return int Current group id which was set by this function
3039  */
3040 function set_current_group($courseid, $groupid) {
3041     global $SESSION;
3043     return $SESSION->currentgroup[$courseid] = $groupid;
3047 /**
3048  * Gets the current group for the current user as an id or an object
3049  *
3050  * @uses $USER
3051  * @uses $SESSION
3052  * @param int $courseid The course being examined - relates to id field in 'course' table.
3053  * @param bool $full If true, the return value is a full record object. If false, just the id of the record.
3054  */
3055 function get_current_group($courseid, $full=false) {
3056     global $SESSION, $USER;
3058     if (!isset($SESSION->currentgroup[$courseid])) {
3059         if (empty($USER->groupmember[$courseid]) or has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_COURSE, $courseid))) {
3061             return 0;
3062         } else {
3063             //trying to add a hack >.<, always first select the first one in list
3064             $SESSION->currentgroup[$courseid] = $USER->groupmember[$courseid][0];
3065         }
3066     }
3068     if ($full) {
3069         return get_record('groups', 'id', $SESSION->currentgroup[$courseid]);
3070     } else {
3071         return $SESSION->currentgroup[$courseid];
3072     }
3075 /**
3076  * A combination function to make it easier for modules
3077  * to set up groups.
3078  *
3079  * It will use a given "groupid" parameter and try to use
3080  * that to reset the current group for the user.
3081  *
3082  * @uses VISIBLEGROUPS
3083  * @param course $course A {@link $COURSE} object
3084  * @param int $groupmode Either NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3085  * @param int $groupid Will try to use this optional parameter to
3086  *            reset the current group for the user
3087  * @return int|false Returns the current group id or false if error.
3088  */
3089 function get_and_set_current_group($course, $groupmode, $groupid=-1) {
3091     if (!$groupmode) {   // Groups don't even apply
3092         return false;
3093     }
3095     $currentgroupid = get_current_group($course->id);
3097     if ($groupid < 0) {  // No change was specified
3098         return $currentgroupid;
3099     }
3101     if ($groupid) {      // Try to change the current group to this groupid
3102         if ($group = get_record('groups', 'id', $groupid, 'courseid', $course->id)) { // Exists
3103             if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_COURSE, $course->id))) {          // Sets current default group
3104                 $currentgroupid = set_current_group($course->id, $group->id);
3106             } else if ($groupmode == VISIBLEGROUPS) {
3107                   // All groups are visible
3108                 //if (ismember($group->id)){
3109                     $currentgroupid = set_current_group($course->id, $group->id);//set this since he might post
3110                 /*)}else {
3111                     $currentgroupid = $group->id;*/
3112             } else if ($groupmode == SEPARATEGROUPS) { // student in separate groups switching
3113                 if (ismember($group->id)){//check if is a member
3114                     $currentgroupid = set_current_group($course->id, $group->id); //might need to set_current_group?
3115                 }
3116                 else {
3117                     echo ($group->id);
3118                     notify('you do not belong to this group!',error);
3119                 }
3120             }
3121         }
3122     } else {             // When groupid = 0 it means show ALL groups
3123         //this is changed, non editting teacher needs access to group 0 as well, for viewing work in visible groups (need to set current group for multiple pages)
3124         if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_COURSE, $course->id)) AND ($groupmode == VISIBLEGROUPS)) {          // Sets current default group
3125             $currentgroupid = set_current_group($course->id, 0);
3127         } else if ($groupmode == VISIBLEGROUPS) {  // All groups are visible
3128             $currentgroupid = 0;
3129         }
3130     }
3132     return $currentgroupid;
3136 /**
3137  * A big combination function to make it easier for modules
3138  * to set up groups.
3139  *
3140  * Terminates if the current user shouldn't be looking at this group
3141  * Otherwise returns the current group if there is one
3142  * Otherwise returns false if groups aren't relevant
3143  *
3144  * @uses SEPARATEGROUPS
3145  * @uses VISIBLEGROUPS
3146  * @param course $course A {@link $COURSE} object
3147  * @param int $groupmode Either NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3148  * @param string $urlroot ?
3149  * @return int|false
3150  */
3151 function setup_and_print_groups($course, $groupmode, $urlroot) {
3153     global $USER, $SESSION; //needs his id, need to hack his groups in session
3155     $changegroup = optional_param('group', -1, PARAM_INT);
3157     $currentgroup = get_and_set_current_group($course, $groupmode, $changegroup);
3158     if ($currentgroup === false) {
3159         return false;
3160     }
3162     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_COURSE, $course->id)) and !$currentgroup) {
3163         //we are in separate groups and the current group is group 0, as last set.
3164         //this can mean that either, this guy has no group
3165         //or, this guy just came from a visible all forum, and he left when he set his current group to 0 (show all)
3167         //for the second situation, we need to perform the trick and get him a group.
3168         $courseid = $course->id;
3169         if (!empty($USER->groupmember[$courseid])){
3170             $currentgroup = get_and_set_current_group($course, $groupmode, $USER->groupmember[$courseid][0]);
3171         }
3172         else {//else he has no group in this course
3173             print_heading(get_string('notingroup'));
3174             print_footer($course);
3175             exit;
3176         }
3177     }
3179     if ($groupmode == VISIBLEGROUPS or ($groupmode and has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_COURSE, $course->id)))) {
3180         if ($groups = get_records_menu('groups', 'courseid', $course->id, 'name ASC', 'id,name')) {
3181             echo '<div align="center">';
3182             print_group_menu($groups, $groupmode, $currentgroup, $urlroot);
3183             echo '</div>';
3184         }
3185     }//added code here to allow non-editting teacher to swap in-between his own groups
3186     //added code for students in separategrous to swtich groups
3187     else if ($groupmode == SEPARATEGROUPS and has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id))) {
3188         $validgroups = array();
3189         //get all the groups this guy is in in this course
3190         if ($p = user_group($course->id,$USER->id)){
3191             //extract the name and id for the group
3192             foreach ($p as $index => $object){
3193                 $validgroups[$object->id] = $object->name;
3194             }
3195             echo '<div align="center">';
3196             //print them in the menu
3197             print_group_menu($validgroups, $groupmode, $currentgroup, $urlroot,0);
3198             echo '</div>';
3199         }
3200     }
3202     return $currentgroup;
3205 function generate_email_processing_address($modid,$modargs) {
3206     global $CFG;
3208     if (empty($CFG->siteidentifier)) {    // Unique site identification code
3209         set_config('siteidentifier', random_string(32));
3210     }
3212     $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3213     return $header . substr(md5($header.$CFG->siteidentifier),0,16).'@'.$CFG->maildomain;
3217 function moodle_process_email($modargs,$body) {
3218     // the first char should be an unencoded letter. We'll take this as an action
3219     switch ($modargs{0}) {
3220         case 'B': { // bounce
3221             list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3222             if ($user = get_record_select("user","id=$userid","id,email")) {
3223                 // check the half md5 of their email
3224                 $md5check = substr(md5($user->email),0,16);
3225                 if ($md5check == substr($modargs, -16)) {
3226                     set_bounce_count($user);
3227                 }
3228                 // else maybe they've already changed it?
3229             }
3230         }
3231         break;
3232         // maybe more later?
3233     }
3236 /// CORRESPONDENCE  ////////////////////////////////////////////////
3238 /**
3239  * Send an email to a specified user
3240  *
3241  * @uses $CFG
3242  * @uses $FULLME
3243  * @uses SITEID
3244  * @param user $user  A {@link $USER} object
3245  * @param user $from A {@link $USER} object
3246  * @param string $subject plain text subject line of the email
3247  * @param string $messagetext plain text version of the message
3248  * @param string $messagehtml complete html version of the message (optional)
3249  * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
3250  * @param string $attachname the name of the file (extension indicates MIME)
3251  * @param bool $usetrueaddress determines whether $from email address should
3252  *          be sent out. Will be overruled by user profile setting for maildisplay
3253  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3254  *          was blocked by user and "false" if there was another sort of error.
3255  */
3256 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='') {
3258     global $CFG, $FULLME;
3260     global $course;                // This is a bit of an ugly hack to be gotten rid of later
3261     if (!empty($course->lang)) {   // Course language is defined
3262         $CFG->courselang = $course->lang;
3263     }
3264     if (!empty($course->theme)) {   // Course theme is defined
3265         $CFG->coursetheme = $course->theme;
3266     }
3268     include_once($CFG->libdir .'/phpmailer/class.phpmailer.php');
3270 /// We are going to use textlib services here
3271     $textlib = textlib_get_instance();
3273     if (empty($user)) {
3274         return false;
3275     }
3277     if (!empty($user->emailstop)) {
3278         return 'emailstop';
3279     }
3281     if (over_bounce_threshold($user)) {
3282         error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
3283         return false;
3284     }
3286     $mail = new phpmailer;
3288     $mail->Version = 'Moodle '. $CFG->version;           // mailer version
3289     $mail->PluginDir = $CFG->libdir .'/phpmailer/';      // plugin directory (eg smtp plugin)
3291     $mail->CharSet = current_charset(true);              //User charset, recalculating it in each call
3293     if ($CFG->smtphosts == 'qmail') {
3294         $mail->IsQmail();                              // use Qmail system
3296     } else if (empty($CFG->smtphosts)) {
3297         $mail->IsMail();                               // use PHP mail() = sendmail
3299     } else {
3300         $mail->IsSMTP();                               // use SMTP directly
3301         if (debugging()) {
3302             echo '<pre>' . "\n";
3303             $mail->SMTPDebug = true;
3304         }
3305         $mail->Host = $CFG->smtphosts;               // specify main and backup servers
3307         if ($CFG->smtpuser) {                          // Use SMTP authentication
3308             $mail->SMTPAuth = true;
3309             $mail->Username = $CFG->smtpuser;
3310             $mail->Password = $CFG->smtppass;
3311         }
3312     }
3314     $adminuser = get_admin();
3316     // make up an email address for handling bounces
3317     if (!empty($CFG->handlebounces)) {
3318         $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
3319         $mail->Sender = generate_email_processing_address(0,$modargs);
3320     }
3321     else {
3322         $mail->Sender   = $adminuser->email;
3323     }
3325     if (is_string($from)) { // So we can pass whatever we want if there is need
3326         $mail->From     = $CFG->noreplyaddress;
3327         $mail->FromName = $from;
3328     } else if ($usetrueaddress and $from->maildisplay) {
3329         $mail->From     = $from->email;
3330         $mail->FromName = fullname($from);
3331     } else {
3332         $mail->From     = $CFG->noreplyaddress;
3333         $mail->FromName = fullname($from);
3334         if (empty($replyto)) {
3335             $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
3336         }
3337     }
3339     if (!empty($replyto)) {
3340         $mail->AddReplyTo($replyto,$replytoname);
3341     }
3343     $mail->Subject = substr(stripslashes($subject), 0, 900);
3345     $mail->AddAddress($user->email, fullname($user) );
3347     $mail->WordWrap = 79;                               // set word wrap
3349     if (!empty($from->customheaders)) {                 // Add custom headers
3350         if (is_array($from->customheaders)) {
3351             foreach ($from->customheaders as $customheader) {
3352                 $mail->AddCustomHeader($customheader);
3353             }
3354         } else {
3355             $mail->AddCustomHeader($from->customheaders);
3356         }
3357     }
3359     if (!empty($from->priority)) {
3360         $mail->Priority = $from->priority;
3361     }
3363     if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
3364         $mail->IsHTML(true);
3365         $mail->Encoding = 'quoted-printable';           // Encoding to use
3366         $mail->Body    =  $messagehtml;
3367         $mail->AltBody =  "\n$messagetext\n";
3368     } else {
3369         $mail->IsHTML(false);
3370         $mail->Body =  "\n$messagetext\n";
3371     }
3373     if ($attachment && $attachname) {
3374         if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
3375             $mail->AddAddress($adminuser->email, fullname($adminuser) );
3376             $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
3377         } else {
3378             require_once($CFG->libdir.'/filelib.php');
3379             $mimetype = mimeinfo('type', $attachname);
3380             $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
3381         }
3382     }
3386 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
3387 /// encoding to the specified one
3388     if (!empty($CFG->unicodedb) && (!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
3389     /// Set it to site mail charset
3390         $charset = $CFG->sitemailcharset;
3391     /// Overwrite it with the user mail charset
3392         if (!empty($CFG->allowusermailcharset)) {
3393             if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
3394                 $charset = $useremailcharset;
3395             }
3396         }
3397     /// If it has changed, convert all the necessary strings
3398         if ($mail->CharSet != $charset) {
3399         /// Save the new mail charset
3400             $mail->CharSet = $charset;
3401         /// And convert some strings
3402             $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
3403             foreach ($mail->ReplyTo as $key => $rt) {                                      //ReplyTo Names
3404                 $mail->ReplyTo[$key][1] = $textlib->convert($rt, 'utf-8', $mail->CharSet);
3405             }
3406             $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet);   //Subject
3407             foreach ($mail->to as $key => $to) {
3408                 $mail->to[$key][1] = $textlib->convert($to, 'utf-8', $mail->CharSet);      //To Names
3409             }
3410             $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet);         //Body
3411             $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet);   //Subject
3412         }
3413     }
3415     if ($mail->Send()) {
3416         set_send_count($user);
3417         return true;
3418     } else {
3419         mtrace('ERROR: '. $mail->ErrorInfo);
3420         add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
3421         return false;
3422     }
3425 /**
3426  * Sets specified user's password and send the new password to the user via email.
3427  *
3428  * @uses $CFG
3429  * @param user $user A {@link $USER} object
3430  * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
3431  *          was blocked by user and "false" if there was another sort of error.
3432  */
3433 function setnew_password_and_mail($user) {
3435     global $CFG;
3437     $site  = get_site();
3438     $from = get_admin();
3440     $newpassword = generate_password();
3442     if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
3443         trigger_error('Could not set user password!');
3444         return false;
3445     }
3447     $a->firstname   = $user->firstname;
3448     $a->sitename    = $site->fullname;
3449     $a->username    = $user->username;
3450     $a->newpassword = $newpassword;
3451     $a->link        = $CFG->wwwroot .'/login/';
3452     $a->signoff     = fullname($from, true).' ('. $from->email .')';
3454     $message = get_string('newusernewpasswordtext', '', $a);
3456     $subject  = $site->fullname .': '. get_string('newusernewpasswordsubj');
3458     return email_to_user($user, $from, $subject, $message);
3462 /**
3463  * Resets specified user's password and send the new password to the user via email.
3464  *
3465  * @uses $CFG
3466  * @param user $user A {@link $USER} object
3467  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3468  *          was blocked by user and "false" if there was another sort of error.
3469  */
3470 function reset_password_and_mail($user) {
3472     global $CFG;
3474     $site  = get_site();
3475     $from = get_admin();
3477     $external = false;
3478     if (!is_internal_auth($user->auth)) {
3479         include_once($CFG->dirroot . '/auth/' . $user->auth . '/lib.php');
3480         if (empty($CFG->{'auth_'.$user->auth.'_stdchangepassword'})
3481             || !function_exists('auth_user_update_password')) {
3482             trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
3483             return false;
3484         } else {
3485             $external = true;
3486         }
3487     }
3489     $newpassword = generate_password();
3491     if ($external) {
3492         if (!auth_user_update_password($user->username, $newpassword)) {
3493             error("Could not set user password!");
3494         }
3495     } else {
3496         if (! set_field("user", "password", md5($newpassword), "id", $user->id) ) {
3497             error("Could not set user password!");
3498         }
3499     }
3501     $a->firstname = $user->firstname;
3502     $a->sitename = $site->fullname;
3503     $a->username = $user->username;
3504     $a->newpassword = $newpassword;
3505     $a->link = $CFG->httpswwwroot .'/login/change_password.php';
3506     $a->signoff = fullname($from, true).' ('. $from->email .')';
3508     $message = get_string('newpasswordtext', '', $a);
3510     $subject  = $site->fullname .': '. get_string('changedpassword');
3512     return email_to_user($user, $from, $subject, $message);
3516 /**
3517  * Send email to specified user with confirmation text and activation link.
3518  *
3519  * @uses $CFG
3520  * @param user $user A {@link $USER} object
3521  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3522  *          was blocked by user and "false" if there was another sort of error.
3523  */
3524  function send_confirmation_email($user) {
3526     global $CFG;
3528     $site = get_site();
3529     $from = get_admin();
3531     $data->firstname = fullname($user);
3532     $data->sitename = $site->fullname;
3533     $data->admin = fullname($from) .' ('. $from->email .')';
3535     $subject = get_string('emailconfirmationsubject', '', $site->fullname);
3537     $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $user->username;
3538     $message     = get_string('emailconfirmation', '', $data);
3539     $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
3541     $user->mailformat = 1;  // Always send HTML version as well
3543     return email_to_user($user, $from, $subject, $message, $messagehtml);
3547 /**
3548  * send_password_change_confirmation_email.
3549  *
3550  * @uses $CFG
3551  * @param user $user A {@link $USER} object
3552  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3553  *          was blocked by user and "false" if there was another sort of error.
3554  */
3555 function send_password_change_confirmation_email($user) {
3557     global $CFG;
3559     $site = get_site();
3560     $from = get_admin();
3562     $data->firstname = $user->firstname;
3563     $data->sitename = $site->fullname;
3564     $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. $user->username;
3565     $data->admin = fullname($from).' ('. $from->email .')';
3567     $message = get_string('emailpasswordconfirmation', '', $data);
3568     $subject = get_string('emailpasswordconfirmationsubject', '', $site->fullname);
3570     return email_to_user($user, $from, $subject, $message);
3574 /**
3575  * Check that an email is allowed.  It returns an error message if there
3576  * was a problem.
3577  *
3578  * @uses $CFG
3579  * @param  string $email Content of email
3580  * @return string|false
3581  */
3582 function email_is_not_allowed($email) {
3584     global $CFG;
3586     if (!empty($CFG->allowemailaddresses)) {
3587         $allowed = explode(' ', $CFG->allowemailaddresses);
3588         foreach ($allowed as $allowedpattern) {
3589             $allowedpattern = trim($allowedpattern);
3590             if (!$allowedpattern) {
3591                 continue;
3592             }
3593             if (strpos(strrev($email), strrev($allowedpattern)) === 0) { // Match!   (bug 5250)
3594                 return false;
3595             }
3596         }
3597         return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
3599     } else if (!empty($CFG->denyemailaddresses)) {
3600         $denied = explode(' ', $CFG->denyemailaddresses);
3601         foreach ($denied as $deniedpattern) {
3602             $deniedpattern = trim($deniedpattern);
3603             if (!$deniedpattern) {
3604                 continue;
3605             }
3606             if (strpos(strrev($email), strrev($deniedpattern)) === 0) { // Match!   (bug 5250)
3607                 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
3608             }
3609         }
3610     }
3612     return false;
3615 function email_welcome_message_to_user($course, $user=NULL) {
3616     global $CFG, $USER;
3618     if (empty($user)) {
3619         if (!isloggedin()) {
3620             return false;
3621         }
3622         $user = $USER;
3623     }
3625     if (!empty($course->welcomemessage)) {
3626         $subject = get_string('welcometocourse', '', $course->fullname);
3628         $a->coursename = $course->fullname;
3629         $a->profileurl = "$CFG->wwwroot/user/view.php?id=$USER->id&course=$course->id";
3630         //$message = get_string("welcometocoursetext", "", $a);
3631         $message = $course->welcomemessage;
3633         if (! $teacher = get_teacher($course->id)) {
3634             $teacher = get_admin();
3635         }
3636         email_to_user($user, $teacher, $subject, $message);
3637     }
3640 /// FILE HANDLING  /////////////////////////////////////////////
3643 /**
3644  * Makes an upload directory for a particular module.
3645  *
3646  * @uses $CFG
3647  * @param int $courseid The id of the course in question - maps to id field of 'course' table.
3648  * @return string|false Returns full path to directory if successful, false if not
3649  */
3650 function make_mod_upload_directory($courseid) {
3651     global $CFG;
3653     if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
3654         return false;
3655     }
3657     $strreadme = get_string('readme');
3659     if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
3660         copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3661     } else {
3662         copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3663     }
3664     return $moddata;
3667 /**
3668  * Returns current name of file on disk if it exists.
3669  *
3670  * @param string $newfile File to be verified
3671  * @return string Current name of file on disk if true
3672  */
3673 function valid_uploaded_file($newfile) {
3674     if (empty($newfile)) {
3675         return '';
3676     }
3677     if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
3678         return $newfile['tmp_name'];
3679     } else {
3680         return '';
3681     }
3684 /**
3685  * Returns the maximum size for uploading files.
3686  *
3687  * There are seven possible upload limits:
3688  * 1. in Apache using LimitRequestBody (no way of checking or changing this)
3689  * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
3690  * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
3691  * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
3692  * 5. by the Moodle admin in $CFG->maxbytes
3693  * 6. by the teacher in the current course $course->maxbytes
3694  * 7. by the teacher for the current module, eg $assignment->maxbytes
3695  *
3696  * These last two are passed to this function as arguments (in bytes).
3697  * Anything defined as 0 is ignored.
3698  * The smallest of all the non-zero numbers is returned.
3699  *
3700  * @param int $sizebytes ?
3701  * @param int $coursebytes Current course $course->maxbytes (in bytes)
3702  * @param int $modulebytes Current module ->maxbytes (in bytes)
3703  * @return int The maximum size for uploading files.
3704  * @todo Finish documenting this function
3705  */
3706 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
3708     if (! $filesize = ini_get('upload_max_filesize')) {
3709         $filesize = '5M';
3710     }
3711     $minimumsize = get_real_size($filesize);
3713     if ($postsize = ini_get('post_max_size')) {
3714         $postsize = get_real_size($postsize);
3715         if ($postsize < $minimumsize) {