removed $SESSION->encoding now replaced by current_charset() and $CFG->unicodedb...
[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_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
142  */
143 define('PARAM_FILE',     0x0010);
145 /**
146  * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
147  * note: the leading slash is not removed, window drive letter is not allowed
148  */
149 define('PARAM_PATH',     0x0020);
151 /**
152  * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
153  */
154 define('PARAM_HOST',     0x0040);
156 /**
157  * PARAM_URL - expected properly formatted URL.
158  */
159 define('PARAM_URL',      0x0080);
161 /**
162  * 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!)
163  */
164 define('PARAM_LOCALURL', 0x0180);
166 /**
167  * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
168  * use when you want to store a new file submitted by students
169  */
170 define('PARAM_CLEANFILE',0x0200);
172 /**
173  * PARAM_ALPHANUM - expected numbers and letters only.
174  */
175 define('PARAM_ALPHANUM', 0x0400);
177 /**
178  * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
179  */
180 define('PARAM_BOOL',     0x0800);
182 /**
183  * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
184  * note: do not forget to addslashes() before storing into database!
185  */
186 define('PARAM_CLEANHTML',0x1000);
188 /**
189  * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
190  * suitable for include() and require()
191  * @TODO: should we rename this function to PARAM_SAFEDIRS??
192  */
193 define('PARAM_ALPHAEXT', 0x2000);
195 /**
196  * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
197  */
198 define('PARAM_SAFEDIR',  0x4000);
200 /// Page types ///
201 /**
202  * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
203  */
204 define('PAGE_COURSE_VIEW', 'course-view');
207 /// PARAMETER HANDLING ////////////////////////////////////////////////////
209 /**
210  * Returns a particular value for the named variable, taken from
211  * POST or GET.  If the parameter doesn't exist then an error is
212  * thrown because we require this variable.
213  *
214  * This function should be used to initialise all required values
215  * in a script that are based on parameters.  Usually it will be
216  * used like this:
217  *    $id = required_param('id');
218  *
219  * @param string $parname the name of the page parameter we want
220  * @param int $type expected type of parameter
221  * @return mixed
222  */
223 function required_param($parname, $type=PARAM_CLEAN) {
225     // detect_unchecked_vars addition
226     global $CFG;
227     if (!empty($CFG->detect_unchecked_vars)) {
228         global $UNCHECKED_VARS;
229         unset ($UNCHECKED_VARS->vars[$parname]);
230     }
232     if (isset($_POST[$parname])) {       // POST has precedence
233         $param = $_POST[$parname];
234     } else if (isset($_GET[$parname])) {
235         $param = $_GET[$parname];
236     } else {
237         error('A required parameter ('.$parname.') was missing');
238     }
240     return clean_param($param, $type);
243 /**
244  * Returns a particular value for the named variable, taken from
245  * POST or GET, otherwise returning a given default.
246  *
247  * This function should be used to initialise all optional values
248  * in a script that are based on parameters.  Usually it will be
249  * used like this:
250  *    $name = optional_param('name', 'Fred');
251  *
252  * @param string $parname the name of the page parameter we want
253  * @param mixed  $default the default value to return if nothing is found
254  * @param int $type expected type of parameter
255  * @return mixed
256  */
257 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
259     // detect_unchecked_vars addition
260     global $CFG;
261     if (!empty($CFG->detect_unchecked_vars)) {
262         global $UNCHECKED_VARS;
263         unset ($UNCHECKED_VARS->vars[$parname]);
264     }
266     if (isset($_POST[$parname])) {       // POST has precedence
267         $param = $_POST[$parname];
268     } else if (isset($_GET[$parname])) {
269         $param = $_GET[$parname];
270     } else {
271         return $default;
272     }
274     return clean_param($param, $type);
277 /**
278  * Used by {@link optional_param()} and {@link required_param()} to
279  * clean the variables and/or cast to specific types, based on
280  * an options field.
281  * <code>
282  * $course->format = clean_param($course->format, PARAM_ALPHA);
283  * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
284  * </code>
285  *
286  * @uses $CFG
287  * @uses PARAM_CLEAN
288  * @uses PARAM_INT
289  * @uses PARAM_INTEGER
290  * @uses PARAM_ALPHA
291  * @uses PARAM_ALPHANUM
292  * @uses PARAM_NOTAGS
293  * @uses PARAM_ALPHATEXT
294  * @uses PARAM_BOOL
295  * @uses PARAM_SAFEDIR
296  * @uses PARAM_CLEANFILE
297  * @uses PARAM_FILE
298  * @uses PARAM_PATH
299  * @uses PARAM_HOST
300  * @uses PARAM_URL
301  * @uses PARAM_LOCALURL
302  * @uses PARAM_CLEANHTML
303  * @param mixed $param the variable we are cleaning
304  * @param int $type expected format of param after cleaning.
305  * @return mixed
306  */
307 function clean_param($param, $type) {
309     global $CFG;
311     if (is_array($param)) {              // Let's loop
312         $newparam = array();
313         foreach ($param as $key => $value) {
314             $newparam[$key] = clean_param($value, $type);
315         }
316         return $newparam;
317     }
319     switch ($type) {
320         case PARAM_RAW:          // no cleaning at all
321             return $param;
323         case PARAM_CLEAN:        // General HTML cleaning, try to use more specific type if possible
324             if (is_numeric($param)) {
325                 return $param;
326             }
327             $param = stripslashes($param);   // Needed for kses to work fine
328             $param = clean_text($param);     // Sweep for scripts, etc
329             return addslashes($param);       // Restore original request parameter slashes
331         case PARAM_CLEANHTML:    // prepare html fragment for display, do not store it into db!!
332             $param = stripslashes($param);   // Remove any slashes
333             $param = clean_text($param);     // Sweep for scripts, etc
334             return trim($param);
336         case PARAM_INT:
337             return (int)$param;  // Convert to integer
339         case PARAM_ALPHA:        // Remove everything not a-z
340             return eregi_replace('[^a-zA-Z]', '', $param);
342         case PARAM_ALPHANUM:     // Remove everything not a-zA-Z0-9
343             return eregi_replace('[^A-Za-z0-9]', '', $param);
345         case PARAM_ALPHAEXT:     // Remove everything not a-zA-Z/_-
346             return eregi_replace('[^a-zA-Z/_-]', '', $param);
348         case PARAM_BOOL:         // Convert to 1 or 0
349             $tempstr = strtolower($param);
350             if ($tempstr == 'on') {
351                 $param = 1;
352             } else if ($tempstr == 'off') {
353                 $param = 0;
354             } else {
355                 $param = empty($param) ? 0 : 1;
356             }
357             return $param;
359         case PARAM_NOTAGS:       // Strip all tags
360             return strip_tags($param);
362         case PARAM_SAFEDIR:      // Remove everything not a-zA-Z0-9_-
363             return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
365         case PARAM_CLEANFILE:    // allow only safe characters
366             return clean_filename($param);
368         case PARAM_FILE:         // Strip all suspicious characters from filename
369             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
370             $param = ereg_replace('\.\.+', '', $param);
371             if($param == '.') {
372                 $param = '';
373             }
374             return $param;
376         case PARAM_PATH:         // Strip all suspicious characters from file path
377             $param = str_replace('\\\'', '\'', $param);
378             $param = str_replace('\\"', '"', $param);
379             $param = str_replace('\\', '/', $param);
380             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
381             $param = ereg_replace('\.\.+', '', $param);
382             $param = ereg_replace('//+', '/', $param);
383             return ereg_replace('/(\./)+', '/', $param);
385         case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
386             preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
387             // match ipv4 dotted quad
388             if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
389                 // confirm values are ok
390                 if ( $match[0] > 255
391                      || $match[1] > 255
392                      || $match[3] > 255
393                      || $match[4] > 255 ) {
394                     // hmmm, what kind of dotted quad is this?
395                     $param = '';
396                 }
397             } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
398                        && !preg_match('/^[\.-]/',  $param) // no leading dots/hyphens
399                        && !preg_match('/[\.-]$/',  $param) // no trailing dots/hyphens
400                        ) {
401                 // all is ok - $param is respected
402             } else {
403                 // all is not ok...
404                 $param='';
405             }
406             return $param;
408         case PARAM_URL:          // allow safe ftp, http, mailto urls
409             include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
410             if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p-f?q?r?')) {
411                 // all is ok, param is respected
412             } else {
413                 $param =''; // not really ok
414             }
415             return $param;
417         case PARAM_LOCALURL:     // allow http absolute, root relative and relative URLs within wwwroot
418             clean_param($param, PARAM_URL);
419             if (!empty($param)) {
420                 if (preg_match(':^/:', $param)) {
421                     // root-relative, ok!
422                 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
423                     // absolute, and matches our wwwroot
424                 } else {
425                     // relative - let's make sure there are no tricks
426                     if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
427                         // looks ok.
428                     } else {
429                         $param = '';
430                     }
431                 }
432             }
433             return $param;
435         default:                 // throw error, switched parameters in optional_param or another serious problem
436             error("Unknown parameter type: $type");
437     }
440 /**
441  * For security purposes, this function will check that the currently
442  * given sesskey (passed as a parameter to the script or this function)
443  * matches that of the current user.
444  *
445  * @param string $sesskey optionally provided sesskey
446  * @return bool
447  */
448 function confirm_sesskey($sesskey=NULL) {
449     global $USER;
451     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
452         return true;
453     }
455     if (empty($sesskey)) {
456         $sesskey = required_param('sesskey');  // Check script parameters
457     }
459     if (!isset($USER->sesskey)) {
460         return false;
461     }
463     return ($USER->sesskey === $sesskey);
467 /**
468  * Ensure that a variable is set
469  *
470  * If $var is undefined throw an error, otherwise return $var.
471  * This function will soon be made obsolete by {@link required_param()}
472  *
473  * @param mixed $var the variable which may be unset
474  * @param mixed $default the value to return if $var is unset
475  */
476 function require_variable($var) {
477     global $CFG;
478     if (!empty($CFG->disableglobalshack)) {
479       error( 'The require_variable() function is deprecated.' );
480     }
481     if (! isset($var)) {
482         error('A required parameter was missing');
483     }
487 /**
488  * Ensure that a variable is set
489  *
490  * If $var is undefined set it (by reference), otherwise return $var.
491  *
492  * @param mixed $var the variable which may be unset
493  * @param mixed $default the value to return if $var is unset
494  */
495 function optional_variable(&$var, $default=0) {
496     global $CFG;
497     if (!empty($CFG->disableglobalshack)) {
498       error( "The optional_variable() function is deprecated ($var, $default)." );
499     }
500     if (! isset($var)) {
501         $var = $default;
502     }
506 /**
507  * Set a key in global configuration
508  *
509  * Set a key/value pair in both this session's {@link $CFG} global variable
510  * and in the 'config' database table for future sessions.
511  *
512  * Can also be used to update keys for plugin-scoped configs in config_plugin table.
513  * In that case it doesn't affect $CFG.
514  *
515  * @param string $name the key to set
516  * @param string $value the value to set
517  * @param string $plugin (optional) the plugin scope
518  * @uses $CFG
519  * @return bool
520  */
521 function set_config($name, $value, $plugin=NULL) {
522 /// No need for get_config because they are usually always available in $CFG
524     global $CFG;
526     if (empty($plugin)) {
527         $CFG->$name = $value;  // So it's defined for this invocation at least
529         if (get_field('config', 'name', 'name', $name)) {
530             return set_field('config', 'value', $value, 'name', $name);
531         } else {
532             $config->name = $name;
533             $config->value = $value;
534             return insert_record('config', $config);
535         }
536     } else { // plugin scope
537         if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
538             return set_field('config_plugins', 'value', $value, 'id', $id);
539         } else {
540             $config->plugin = $plugin;
541             $config->name   = $name;
542             $config->value  = $value;
543             return insert_record('config_plugins', $config);
544         }
545     }
548 /**
549  * Get configuration values from the global config table
550  * or the config_plugins table.
551  *
552  * If called with no parameters it will do the right thing
553  * generating $CFG safely from the database without overwriting
554  * existing values.
555  *
556  * @param string $plugin
557  * @param string $name
558  * @uses $CFG
559  * @return hash-like object or single value
560  *
561  */
562 function get_config($plugin=NULL, $name=NULL) {
564     global $CFG;
566     if (!empty($name)) { // the user is asking for a specific value
567         if (!empty($plugin)) {
568             return get_record('config_plugins', 'plugin' , $plugin, 'name', $name);
569         } else {
570             return get_record('config', 'name', $name);
571         }
572     }
574     // the user is after a recordset
575     if (!empty($plugin)) {
576         if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
577             $configs = (array)$configs;
578             $localcfg = array();
579             foreach ($configs as $config) {
580                 $localcfg[$config->name] = $config->value;
581             }
582             return (object)$localcfg;
583         } else {
584             return false;
585         }
586     } else {
587         // this was originally in setup.php
588         if ($configs = get_records('config')) {
589             $localcfg = (array)$CFG;
590             foreach ($configs as $config) {
591                 if (!isset($localcfg[$config->name])) {
592                     $localcfg[$config->name] = $config->value;
593                 } else {
594                     if ($localcfg[$config->name] != $config->value ) {
595                         // complain if the DB has a different
596                         // value than config.php does
597                         error_log("\$CFG->{$config->name} in config.php ({$localcfg[$config->name]}) overrides database setting ({$config->value})");
598                     }
599                 }
600             }
602             $localcfg = (object)$localcfg;
603             return $localcfg;
604         } else {
605             // preserve $CFG if DB returns nothing or error
606             return $CFG;
607         }
609     }
613 /**
614  * Refresh current $USER session global variable with all their current preferences.
615  * @uses $USER
616  */
617 function reload_user_preferences() {
619     global $USER;
621     if(empty($USER) || empty($USER->id)) {
622         return false;
623     }
625     unset($USER->preference);
627     if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
628         foreach ($preferences as $preference) {
629             $USER->preference[$preference->name] = $preference->value;
630         }
631     } else {
632             //return empty preference array to hold new values
633             $USER->preference = array();
634     }
637 /**
638  * Sets a preference for the current user
639  * Optionally, can set a preference for a different user object
640  * @uses $USER
641  * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
643  * @param string $name The key to set as preference for the specified user
644  * @param string $value The value to set forthe $name key in the specified user's record
645  * @param int $userid A moodle user ID
646  * @return bool
647  */
648 function set_user_preference($name, $value, $otheruser=NULL) {
650     global $USER;
652     if (empty($otheruser)){
653         if (!empty($USER) && !empty($USER->id)) {
654             $userid = $USER->id;
655         } else {
656             return false;
657         }
658     } else {
659         $userid = $otheruser;
660     }
662     if (empty($name)) {
663         return false;
664     }
666     if ($preference = get_record('user_preferences', 'userid', $userid, 'name', $name)) {
667         if (set_field('user_preferences', 'value', $value, 'id', $preference->id)) {
668             if (empty($otheruser) and !empty($USER)) {
669                 $USER->preference[$name] = $value;
670             }
671             return true;
672         } else {
673             return false;
674         }
676     } else {
677         $preference->userid = $userid;
678         $preference->name   = $name;
679         $preference->value  = (string)$value;
680         if (insert_record('user_preferences', $preference)) {
681             if (empty($otheruser) and !empty($USER)) {
682                 $USER->preference[$name] = $value;
683             }
684             return true;
685         } else {
686             return false;
687         }
688     }
691 /**
692  * Unsets a preference completely by deleting it from the database
693  * Optionally, can set a preference for a different user id
694  * @uses $USER
695  * @param string  $name The key to unset as preference for the specified user
696  * @param int $userid A moodle user ID
697  * @return bool
698  */
699 function unset_user_preference($name, $userid=NULL) {
701     global $USER;
703     if (empty($userid)){
704         if(!empty($USER) && !empty($USER->id)) {
705             $userid = $USER->id;
706         }
707         else {
708             return false;
709         }
710     }
712     //Delete the preference from $USER
713     if (isset($USER->preference[$name])) {
714         unset($USER->preference[$name]);
715     }
717     //Then from DB
718     return delete_records('user_preferences', 'userid', $userid, 'name', $name);
722 /**
723  * Sets a whole array of preferences for the current user
724  * @param array $prefarray An array of key/value pairs to be set
725  * @param int $userid A moodle user ID
726  * @return bool
727  */
728 function set_user_preferences($prefarray, $userid=NULL) {
730     global $USER;
732     if (!is_array($prefarray) or empty($prefarray)) {
733         return false;
734     }
736     if (empty($userid)){
737         if (!empty($USER) && !empty($USER->id)) {
738             $userid = NULL;  // Continue with the current user below
739         } else {
740             return false;    // No-one to set!
741         }
742     }
744     $return = true;
745     foreach ($prefarray as $name => $value) {
746         // The order is important; if the test for return is done first, then
747         // if one function call fails all the remaining ones will be "optimized away"
748         $return = set_user_preference($name, $value, $userid) and $return;
749     }
750     return $return;
753 /**
754  * If no arguments are supplied this function will return
755  * all of the current user preferences as an array.
756  * If a name is specified then this function
757  * attempts to return that particular preference value.  If
758  * none is found, then the optional value $default is returned,
759  * otherwise NULL.
760  * @param string $name Name of the key to use in finding a preference value
761  * @param string $default Value to be returned if the $name key is not set in the user preferences
762  * @param int $userid A moodle user ID
763  * @uses $USER
764  * @return string
765  */
766 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
768     global $USER;
770     if (empty($userid)) {   // assume current user
771         if (empty($USER->preference)) {
772             return $default;              // Default value (or NULL)
773         }
774         if (empty($name)) {
775             return $USER->preference;     // Whole array
776         }
777         if (!isset($USER->preference[$name])) {
778             return $default;              // Default value (or NULL)
779         }
780         return $USER->preference[$name];  // The single value
782     } else {
783         $preference = get_records_menu('user_preferences', 'userid', $userid, 'name', 'name,value');
785         if (empty($name)) {
786             return $preference;
787         }
788         if (!isset($preference[$name])) {
789             return $default;              // Default value (or NULL)
790         }
791         return $preference[$name];        // The single value
792     }
796 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
798 /**
799  * Given date parts in user time produce a GMT timestamp.
800  *
801  * @param int $year The year part to create timestamp of
802  * @param int $month The month part to create timestamp of
803  * @param int $day The day part to create timestamp of
804  * @param int $hour The hour part to create timestamp of
805  * @param int $minute The minute part to create timestamp of
806  * @param int $second The second part to create timestamp of
807  * @param float $timezone ?
808  * @param bool $applydst ?
809  * @return int timestamp
810  * @todo Finish documenting this function
811  */
812 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
814     $timezone = get_user_timezone_offset($timezone);
816     if (abs($timezone) > 13) {
817         $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
818     } else {
819         $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
820         $time = usertime($time, $timezone);
821         if($applydst) {
822             $time -= dst_offset_on($time);
823         }
824     }
826     return $time;
830 /**
831  * Given an amount of time in seconds, returns string
832  * formatted nicely as months, days, hours etc as needed
833  *
834  * @uses MINSECS
835  * @uses HOURSECS
836  * @uses DAYSECS
837  * @param int $totalsecs ?
838  * @param array $str ?
839  * @return string
840  * @todo Finish documenting this function
841  */
842  function format_time($totalsecs, $str=NULL) {
844     $totalsecs = abs($totalsecs);
846     if (!$str) {  // Create the str structure the slow way
847         $str->day   = get_string('day');
848         $str->days  = get_string('days');
849         $str->hour  = get_string('hour');
850         $str->hours = get_string('hours');
851         $str->min   = get_string('min');
852         $str->mins  = get_string('mins');
853         $str->sec   = get_string('sec');
854         $str->secs  = get_string('secs');
855     }
857     $days      = floor($totalsecs/DAYSECS);
858     $remainder = $totalsecs - ($days*DAYSECS);
859     $hours     = floor($remainder/HOURSECS);
860     $remainder = $remainder - ($hours*HOURSECS);
861     $mins      = floor($remainder/MINSECS);
862     $secs      = $remainder - ($mins*MINSECS);
864     $ss = ($secs == 1)  ? $str->sec  : $str->secs;
865     $sm = ($mins == 1)  ? $str->min  : $str->mins;
866     $sh = ($hours == 1) ? $str->hour : $str->hours;
867     $sd = ($days == 1)  ? $str->day  : $str->days;
869     $odays = '';
870     $ohours = '';
871     $omins = '';
872     $osecs = '';
874     if ($days)  $odays  = $days .' '. $sd;
875     if ($hours) $ohours = $hours .' '. $sh;
876     if ($mins)  $omins  = $mins .' '. $sm;
877     if ($secs)  $osecs  = $secs .' '. $ss;
879     if ($days)  return $odays .' '. $ohours;
880     if ($hours) return $ohours .' '. $omins;
881     if ($mins)  return $omins .' '. $osecs;
882     if ($secs)  return $osecs;
883     return get_string('now');
886 /**
887  * Returns a formatted string that represents a date in user time
888  * <b>WARNING: note that the format is for strftime(), not date().</b>
889  * Because of a bug in most Windows time libraries, we can't use
890  * the nicer %e, so we have to use %d which has leading zeroes.
891  * A lot of the fuss in the function is just getting rid of these leading
892  * zeroes as efficiently as possible.
893  *
894  * If parameter fixday = true (default), then take off leading
895  * zero from %d, else mantain it.
896  *
897  * @uses HOURSECS
898  * @param  int $date timestamp in GMT
899  * @param string $format strftime format
900  * @param float $timezone
901  * @param bool $fixday If true (default) then the leading
902  * zero from %d is removed. If false then the leading zero is mantained.
903  * @return string
904  */
905 function userdate($date, $format='', $timezone=99, $fixday = true) {
907     global $CFG;
909     static $strftimedaydatetime;
911     if ($format == '') {
912         if (empty($strftimedaydatetime)) {
913             $strftimedaydatetime = get_string('strftimedaydatetime');
914         }
915         $format = $strftimedaydatetime;
916     }
918     if (!empty($CFG->nofixday)) {  // Config.php can force %d not to be fixed.
919         $fixday = false;
920     } else if ($fixday) {
921         $formatnoday = str_replace('%d', 'DD', $format);
922         $fixday = ($formatnoday != $format);
923     }
925     $date += dst_offset_on($date);
927     $timezone = get_user_timezone_offset($timezone);
929     if (abs($timezone) > 13) {   /// Server time
930         if ($fixday) {
931             $datestring = strftime($formatnoday, $date);
932             $daystring  = str_replace(' 0', '', strftime(' %d', $date));
933             $datestring = str_replace('DD', $daystring, $datestring);
934         } else {
935             $datestring = strftime($format, $date);
936         }
937     } else {
938         $date += (int)($timezone * 3600);
939         if ($fixday) {
940             $datestring = gmstrftime($formatnoday, $date);
941             $daystring  = str_replace(' 0', '', gmstrftime(' %d', $date));
942             $datestring = str_replace('DD', $daystring, $datestring);
943         } else {
944             $datestring = gmstrftime($format, $date);
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 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;
1442 /* this function forces a user to log out */
1444 function require_logout() {
1445     global $USER, $CFG;
1446     if (isset($USER) and isset($USER->id)) {
1447         add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
1449         if ($USER->auth == 'cas' && !empty($CFG->cas_enabled)) {
1450             require($CFG->dirroot.'/auth/cas/logout.php');
1451         }
1452     }
1454     if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
1455         // This method is just to try to avoid silly warnings from PHP 4.3.0
1456         session_unregister("USER");
1457         session_unregister("SESSION");
1458     }
1460     setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, '/');
1461     unset($_SESSION['USER']);
1462     unset($_SESSION['SESSION']);
1464     unset($SESSION);
1465     unset($USER);
1468 /**
1469  * This function checks that the current user is logged in and has the
1470  * required privileges
1471  *
1472  * This function checks that the current user is logged in, and optionally
1473  * whether they are allowed to be in a particular course and view a particular
1474  * course module.
1475  * If they are not logged in, then it redirects them to the site login unless
1476  * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1477  * case they are automatically logged in as guests.
1478  * If $courseid is given and the user is not enrolled in that course then the
1479  * user is redirected to the course enrolment page.
1480  * If $cm is given and the coursemodule is hidden and the user is not a teacher
1481  * in the course then the user is redirected to the course home page.
1482  *
1483  * @uses $CFG
1484  * @uses $SESSION
1485  * @uses $USER
1486  * @uses $FULLME
1487  * @uses SITEID
1488  * @uses $MoodleSession
1489  * @param int $courseid id of the course
1490  * @param bool $autologinguest
1491  * @param object $cm course module object
1492  */
1493 function require_login($courseid=0, $autologinguest=true, $cm=null) {
1495     global $CFG, $SESSION, $USER, $FULLME, $MoodleSession;
1497     // First check that the user is logged in to the site.
1498     if (! (isset($USER->loggedin) and $USER->confirmed and ($USER->site == $CFG->wwwroot)) ) { // They're not
1499         $SESSION->wantsurl = $FULLME;
1500         if (!empty($_SERVER['HTTP_REFERER'])) {
1501             $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
1502         }
1503         $USER = NULL;
1504         if ($autologinguest and $CFG->autologinguests and $courseid and ($courseid == SITEID or get_field('course','guest','id',$courseid)) ) {
1505             $loginguest = '?loginguest=true';
1506         } else {
1507             $loginguest = '';
1508         }
1509         if (empty($CFG->loginhttps)) {
1510             redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1511         } else {
1512             $wwwroot = str_replace('http','https', $CFG->wwwroot);
1513             redirect($wwwroot .'/login/index.php'. $loginguest);
1514         }
1515         exit;
1516     }
1518     // check whether the user should be changing password
1519     // reload_user_preferences();    // Why is this necessary?  Seems wasteful.  - MD
1520     if (!empty($USER->preference['auth_forcepasswordchange'])){
1521         if (is_internal_auth() || $CFG->{'auth_'.$USER->auth.'_stdchangepassword'}){
1522             $SESSION->wantsurl = $FULLME;
1523             redirect($CFG->wwwroot .'/login/change_password.php');
1524         } elseif($CFG->changepassword) {
1525             redirect($CFG->changepassword);
1526         } else {
1527             error('You cannot proceed without changing your password.
1528                    However there is no available page for changing it.
1529                    Please contact your Moodle Administrator.');
1530         }
1531     }
1532     // Check that the user account is properly set up
1533     if (user_not_fully_set_up($USER)) {
1534         $SESSION->wantsurl = $FULLME;
1535         redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1536     }
1538     // Make sure current IP matches the one for this session (if required)
1539     if (!empty($CFG->tracksessionip)) {
1540         if ($USER->sessionIP != md5(getremoteaddr())) {
1541             error(get_string('sessionipnomatch', 'error'));
1542         }
1543     }
1545     // Make sure the USER has a sesskey set up.  Used for checking script parameters.
1546     sesskey();
1548     // Check that the user has agreed to a site policy if there is one
1549     if (!empty($CFG->sitepolicy)) {
1550         if (!$USER->policyagreed) {
1551             $SESSION->wantsurl = $FULLME;
1552             redirect($CFG->wwwroot .'/user/policy.php');
1553         }
1554     }
1556     // If the site is currently under maintenance, then print a message
1557     if (!isadmin()) {
1558         if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1559             print_maintenance_message();
1560             exit;
1561         }
1562     }
1564     // Next, check if the user can be in a particular course
1565     if ($courseid) {
1566         if ($courseid == SITEID) { // Anyone can be in the site course
1567             if (isset($cm) and !$cm->visible and !isteacher(SITEID)) { // Not allowed to see module, send to course page
1568                 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1569             }
1570             return;
1571         }
1572         if (! $course = get_record('course', 'id', $courseid)) {
1573             error('That course doesn\'t exist');
1574         }
1575         if (!(isteacher($courseid) || !empty($USER->admin)) && (!$course->visible || !course_parent_visible($course))) {
1576             print_header();
1577             notice(get_string('coursehidden'), $CFG->wwwroot .'/');
1578         }
1579         if (!empty($USER->student[$courseid]) or !empty($USER->teacher[$courseid]) or !empty($USER->admin)) {
1580             if (isset($USER->realuser)) {   // Make sure the REAL person can also access this course
1581                 if (!isteacher($courseid, $USER->realuser)) {
1582                     print_header();
1583                     notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
1584                 }
1585             }
1586             if (isset($cm) and !$cm->visible and !isteacher($courseid)) { // Not allowed to see module, send to course page
1587                 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1588             }
1589             return;   // user is a member of this course.
1590         }
1591         if ($USER->username == 'guest') {
1592             switch ($course->guest) {
1593                 case 0: // Guests not allowed
1594                     print_header();
1595                     notice(get_string('guestsnotallowed', '', $course->fullname), "$CFG->wwwroot/login/index.php");
1596                     break;
1597                 case 1: // Guests allowed
1598                     if (isset($cm) and !$cm->visible) { // Not allowed to see module, send to course page
1599                         redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1600                     }
1601                     return;
1602                 case 2: // Guests allowed with key (drop through)
1603                     break;
1604             }
1605         }
1607         //User is not enrolled in the course, wants to access course content
1608         //as a guest, and course setting allow unlimited guest access
1609         //Code cribbed from course/loginas.php
1610         if (strstr($FULLME,"username=guest") && ($course->guest==1)) {
1611             $realuser = $USER->id;
1612             $realname = fullname($USER, true);
1613             $USER = guest_user();
1614             $USER->loggedin = true;
1615             $USER->site = $CFG->wwwroot;
1616             $USER->realuser = $realuser;
1617             $USER->sessionIP = md5(getremoteaddr());   // Store the current IP in the session
1618             if (isset($SESSION->currentgroup)) {    // Remember current cache setting for later
1619                 $SESSION->oldcurrentgroup = $SESSION->currentgroup;
1620                 unset($SESSION->currentgroup);
1621             }
1622             $guest_name = fullname($USER, true);
1623             add_to_log($course->id, "course", "loginas", "../user/view.php?id=$course->id&$USER->id$", "$realname -> $guest_name");
1624             if (isset($cm) and !$cm->visible) { // Not allowed to see module, send to course page
1625                 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1626             }
1627             return;
1628         }
1630         // Currently not enrolled in the course, so see if they want to enrol
1631         $SESSION->wantsurl = $FULLME;
1632         redirect($CFG->wwwroot .'/course/enrol.php?id='. $courseid);
1633         die;
1634     }
1637 /**
1638  * This is a weaker version of {@link require_login()} which only requires login
1639  * when called from within a course rather than the site page, unless
1640  * the forcelogin option is turned on.
1641  *
1642  * @uses $CFG
1643  * @param object $course The course object in question
1644  * @param bool $autologinguest Allow autologin guests if that is wanted
1645  * @param object $cm Course activity module if known
1646  */
1647 function require_course_login($course, $autologinguest=true, $cm=null) {
1648     global $CFG;
1649     if (!empty($CFG->forcelogin)) {
1650         require_login();
1651     }
1652     if ($course->id != SITEID) {
1653         require_login($course->id, $autologinguest, $cm);
1654     }
1657 /**
1658  * Modify the user table by setting the currently logged in user's
1659  * last login to now.
1660  *
1661  * @uses $USER
1662  * @return bool
1663  */
1664 function update_user_login_times() {
1665     global $USER;
1667     $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
1668     $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
1670     $user->id = $USER->id;
1672     return update_record('user', $user);
1675 /**
1676  * Determines if a user has completed setting up their account.
1677  *
1678  * @param user $user A {@link $USER} object to test for the existance of a valid name and email
1679  * @return bool
1680  */
1681 function user_not_fully_set_up($user) {
1682     return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
1685 function over_bounce_threshold($user) {
1687     global $CFG;
1689     if (empty($CFG->handlebounces)) {
1690         return false;
1691     }
1692     // set sensible defaults
1693     if (empty($CFG->minbounces)) {
1694         $CFG->minbounces = 10;
1695     }
1696     if (empty($CFG->bounceratio)) {
1697         $CFG->bounceratio = .20;
1698     }
1699     $bouncecount = 0;
1700     $sendcount = 0;
1701     if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1702         $bouncecount = $bounce->value;
1703     }
1704     if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1705         $sendcount = $send->value;
1706     }
1707     return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
1710 /**
1711  * @param $user - object containing an id
1712  * @param $reset - will reset the count to 0
1713  */
1714 function set_send_count($user,$reset=false) {
1715     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1716         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1717         update_record('user_preferences',$pref);
1718     }
1719     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1720         // make a new one
1721         $pref->name = 'email_send_count';
1722         $pref->value = 1;
1723         $pref->userid = $user->id;
1724         insert_record('user_preferences',$pref, false);
1725     }
1728 /**
1729 * @param $user - object containing an id
1730  * @param $reset - will reset the count to 0
1731  */
1732 function set_bounce_count($user,$reset=false) {
1733     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1734         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1735         update_record('user_preferences',$pref);
1736     }
1737     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1738         // make a new one
1739         $pref->name = 'email_bounce_count';
1740         $pref->value = 1;
1741         $pref->userid = $user->id;
1742         insert_record('user_preferences',$pref, false);
1743     }
1746 /**
1747  * Keeps track of login attempts
1748  *
1749  * @uses $SESSION
1750  */
1751 function update_login_count() {
1753     global $SESSION;
1755     $max_logins = 10;
1757     if (empty($SESSION->logincount)) {
1758         $SESSION->logincount = 1;
1759     } else {
1760         $SESSION->logincount++;
1761     }
1763     if ($SESSION->logincount > $max_logins) {
1764         unset($SESSION->wantsurl);
1765         error(get_string('errortoomanylogins'));
1766     }
1769 /**
1770  * Resets login attempts
1771  *
1772  * @uses $SESSION
1773  */
1774 function reset_login_count() {
1775     global $SESSION;
1777     $SESSION->logincount = 0;
1780 /**
1781  * check_for_restricted_user
1782  *
1783  * @uses $CFG
1784  * @uses $USER
1785  * @param string $username ?
1786  * @param string $redirect ?
1787  * @todo Finish documenting this function
1788  */
1789 function check_for_restricted_user($username=NULL, $redirect='') {
1790     global $CFG, $USER;
1792     if (!$username) {
1793         if (!empty($USER->username)) {
1794             $username = $USER->username;
1795         } else {
1796             return false;
1797         }
1798     }
1800     if (!empty($CFG->restrictusers)) {
1801         $names = explode(',', $CFG->restrictusers);
1802         if (in_array($username, $names)) {
1803             error(get_string('restricteduser', 'error', fullname($USER)), $redirect);
1804         }
1805     }
1808 function is_restricted_user($username){
1809     global $CFG;
1811     if (!empty($CFG->restrictusers)) {
1812         $names = explode(',', $CFG->restrictusers);
1813         if (in_array($username, $names)) {
1814             return true;
1815         }
1816     }
1817     return false;
1820 function sync_metacourses() {
1822     global $CFG;
1824     if (!$courses = get_records_sql("SELECT DISTINCT parent_course,1 FROM {$CFG->prefix}course_meta")) {
1825         return;
1826     }
1828     foreach ($courses as $course) {
1829         sync_metacourse($course->parent_course);
1830     }
1834 /**
1835  * Goes through all enrolment records for the courses inside the metacourse and sync with them.
1836  */
1838 function sync_metacourse($metacourseid) {
1840     global $CFG;
1842     if (!$metacourse = get_record("course","id",$metacourseid)) {
1843         return false;
1844     }
1846     if (count_records('course_meta','parent_course',$metacourseid) == 0) {
1847         // if there are no child courses for this meta course, nuke the enrolments
1848         if ($enrolments = get_records('user_students','course',$metacourseid,'','userid,1')) {
1849             foreach ($enrolments as $enrolment) {
1850                 unenrol_student($enrolment->userid,$metacourseid);
1851             }
1852         }
1853         return true;
1854     }
1856     // first get the list of child courses
1857     $c_courses = get_records('course_meta','parent_course',$metacourseid);
1858     $instr = '';
1859     foreach ($c_courses as $c) {
1860         $instr .= $c->child_course.',';
1861     }
1862     $instr = substr($instr,0,-1);
1864     // now get the list of valid enrolments in the child courses
1865     $sql = 'SELECT DISTINCT userid,1 FROM '.$CFG->prefix.'user_students WHERE course IN ('.$instr.')';
1866     $enrolments = get_records_sql($sql);
1868     // put it into a nice array we can happily use array_diff on.
1869     $ce = array();
1870     if (!empty($enrolments)) {
1871         foreach ($enrolments as $en) {
1872             $ce[] = $en->userid;
1873         }
1874     }
1876     // now get the list of current enrolments in the meta course.
1877     $sql = 'SELECT userid,1 FROM '.$CFG->prefix.'user_students WHERE course = '.$metacourseid;
1878     $enrolments = get_records_sql($sql);
1880     $me = array();
1881     if (!empty($enrolments)) {
1882         foreach ($enrolments as $en) {
1883             $me[] = $en->userid;
1884         }
1885     }
1887     $enrolmentstodelete = array_diff($me,$ce);
1888     $userstoadd = array_diff($ce,$me);
1890     foreach ($enrolmentstodelete as $userid) {
1891         unenrol_student($userid,$metacourseid);
1892     }
1893     foreach ($userstoadd as $userid) {
1894         enrol_student($userid,$metacourseid,0,0,'metacourse');
1895     }
1897     // and next make sure that we have the right start time and end time (ie max and min) for them all.
1898     if ($enrolments = get_records('user_students','course',$metacourseid,'','id,userid')) {
1899         foreach ($enrolments as $enrol) {
1900             if ($maxmin = get_record_sql("SELECT min(timestart) AS timestart, max(timeend) AS timeend
1901                FROM {$CFG->prefix}user_students u,
1902                     {$CFG->prefix}course_meta mc
1903                WHERE u.course = mc.child_course
1904                AND userid = $enrol->userid
1905                AND mc.parent_course = $metacourseid")) {
1906                 $enrol->timestart = $maxmin->timestart;
1907                 $enrol->timeend = $maxmin->timeend;
1908                 $enrol->enrol = 'metacourse'; // just in case it wasn't there earlier.
1909                 update_record('user_students',$enrol);
1910             }
1911         }
1912     }
1913     return true;
1917 /**
1918  * Adds a record to the metacourse table and calls sync_metacoures
1919  */
1920 function add_to_metacourse ($metacourseid, $courseid) {
1922     if (!$metacourse = get_record("course","id",$metacourseid)) {
1923         return false;
1924     }
1926     if (!$course = get_record("course","id",$courseid)) {
1927         return false;
1928     }
1930     if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
1931         $rec->parent_course = $metacourseid;
1932         $rec->child_course = $courseid;
1933         if (!insert_record('course_meta',$rec)) {
1934             return false;
1935         }
1936         return sync_metacourse($metacourseid);
1937     }
1938     return true;
1942 /**
1943  * Removes the record from the metacourse table and calls sync_metacourse
1944  */
1945 function remove_from_metacourse($metacourseid, $courseid) {
1947     if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
1948         return sync_metacourse($metacourseid);
1949     }
1950     return false;
1954 /**
1955  * Determines if a user is currently logged in
1956  *
1957  * @uses $USER
1958  * @return bool
1959  */
1960 function isloggedin() {
1961     global $USER;
1963     return (!empty($USER->id));
1967 /**
1968  * Determines if a user an admin
1969  *
1970  * @uses $USER
1971  * @param int $userid The id of the user as is found in the 'user' table
1972  * @staticvar array $admins List of users who have been found to be admins by user id
1973  * @staticvar array $nonadmins List of users who have been found not to be admins by user id
1974  * @return bool
1975  */
1976 function isadmin($userid=0) {
1977     global $USER;
1978     static $admins, $nonadmins;
1980     if (!isset($admins)) {
1981         $admins = array();
1982         $nonadmins = array();
1983     }
1985     if (!$userid){
1986         if (empty($USER->id)) {
1987             return false;
1988         }
1989         $userid = $USER->id;
1990     }
1992     if (!empty($USER->id) and ($userid == $USER->id)) {  // Check session cache
1993         return !empty($USER->admin);
1994     }
1996     if (in_array($userid, $admins)) {
1997         return true;
1998     } else if (in_array($userid, $nonadmins)) {
1999         return false;
2000     } else if (record_exists('user_admins', 'userid', $userid)){
2001         $admins[] = $userid;
2002         return true;
2003     } else {
2004         $nonadmins[] = $userid;
2005         return false;
2006     }
2009 /**
2010  * Determines if a user is a teacher (or better)
2011  *
2012  * @uses $USER
2013  * @uses $CFG
2014  * @param int $courseid The id of the course that is being viewed, if any
2015  * @param int $userid The id of the user that is being tested against. Set this to 0 if you would just like to test against the currently logged in user.
2016  * @param bool $includeadmin If true this function will return true when it encounters an admin user.
2017  * @return bool
2018  */
2019 function isteacher($courseid=0, $userid=0, $includeadmin=true) {
2020 /// Is the user able to access this course as a teacher?
2021     global $USER, $CFG;
2023     if (empty($userid)) {                           // we are relying on $USER
2024         if (empty($USER) or empty($USER->id)) {     // not logged in so can't be a teacher
2025             return false;
2026         }
2027         if (!empty($USER->studentview)) {
2028             return false;
2029         }
2030         if (!empty($USER->teacher) and $courseid) {   // look in session cache
2031             if (!empty($USER->teacher[$courseid])) {  // Explicitly a teacher, good
2032                 return true;
2033             }
2034         }
2035         $userid = $USER->id;                        // we need to make further checks
2036     }
2038     if ($includeadmin and isadmin($userid)) {   // admins can do anything the teacher can
2039         return true;
2040     }
2042     if (empty($courseid)) {                     // should not happen, but we handle it
2043         if (isadmin() or $CFG->debug > 7) {
2044             notify('Coding error: isteacher() should not be used without a valid course id '.
2045                    'as argument.  Please notify the developer for this module.');
2046         }
2047         return isteacherinanycourse($userid, $includeadmin);
2048     }
2050 /// Last resort, check the database
2052     return record_exists('user_teachers', 'userid', $userid, 'course', $courseid);
2055 /**
2056  * Determines if a user is a teacher in any course, or an admin
2057  *
2058  * @uses $USER
2059  * @param int $userid The id of the user that is being tested against. Set this to 0 if you would just like to test against the currently logged in user.
2060  * @param bool $includeadmin If true this function will return true when it encounters an admin user.
2061  * @return bool
2062  */
2063 function isteacherinanycourse($userid=0, $includeadmin=true) {
2064     global $USER;
2066     if (empty($userid)) {
2067         if (empty($USER) or empty($USER->id)) {
2068             return false;
2069         }
2070         if (!empty($USER->teacher)) {   // look in session cache
2071             return true;
2072         }
2073         $userid = $USER->id;
2074     }
2076     if ($includeadmin and isadmin($userid)) {  // admins can do anything
2077         return true;
2078     }
2080     return record_exists('user_teachers', 'userid', $userid);
2083 /**
2084  * Determines if a user is allowed to edit a given course
2085  *
2086  * @uses $USER
2087  * @param int $courseid The id of the course that is being edited
2088  * @param int $userid The id of the user that is being tested against. Set this to 0 if you would just like to test against the currently logged in user.
2089  * @param bool $ignorestudentview true = don't do check for studentview mode
2090  * @return boo
2091  */
2092 function isteacheredit($courseid, $userid=0, $ignorestudentview=false) {
2093     global $USER;
2095     // we can't edit in studentview
2096     if (!empty($USER->studentview) and !$ignorestudentview) {
2097         return false;
2098     }
2100     if (isadmin($userid)) {  // admins can do anything
2101         return true;
2102     }
2104     if (!$userid) {
2105         if (empty($USER) or empty($USER->id)) {     // not logged in so can't be a teacher
2106             return false;
2107         }
2108         if (empty($USER->teacheredit)) {            // we are relying on session cache
2109             return false;
2110         }
2111         return !empty($USER->teacheredit[$courseid]);
2112     }
2114     return get_field('user_teachers', 'editall', 'userid', $userid, 'course', $courseid);
2117 /**
2118  * Determines if a user can create new courses
2119  *
2120  * @uses $USER
2121  * @param int $userid The user being tested. You can set this to 0 or leave it blank to test the currently logged in user.
2122  * @return bool
2123  */
2124 function iscreator ($userid=0) {
2125     global $USER;
2126     if (empty($USER->id)) {
2127         return false;
2128     }
2129     if (isadmin($userid)) {  // admins can do anything
2130         return true;
2131     }
2132     if (empty($userid)) {
2133         return record_exists('user_coursecreators', 'userid', $USER->id);
2134     }
2136     return record_exists('user_coursecreators', 'userid', $userid);
2139 /**
2140  * Determines if a user is a student in the specified course
2141  *
2142  * If the course id specifies the site then the function determines
2143  * if the user is a confirmed and valid user of this site.
2144  *
2145  * @uses $USER
2146  * @uses $CFG
2147  * @uses SITEID
2148  * @param int $courseid The id of the course being tested
2149  * @param int $userid The user being tested. You can set this to 0 or leave it blank to test the currently logged in user.
2150  * @return bool
2151  */
2152 function isstudent($courseid, $userid=0) {
2153     global $USER, $CFG;
2155     if (empty($USER->id) and !$userid) {
2156         return false;
2157     }
2159     if ($courseid == SITEID) {
2160         if (!$userid) {
2161             $userid = $USER->id;
2162         }
2163         if (isguest($userid)) {
2164             return false;
2165         }
2166         // a site teacher can never be a site student
2167         if (isteacher($courseid, $userid)) {
2168             return false;
2169         }
2170         if ($CFG->allusersaresitestudents) {
2171             return record_exists('user', 'id', $userid);
2172         } else {
2173             return (record_exists('user_students', 'userid', $userid)
2174                      or record_exists('user_teachers', 'userid', $userid));
2175         }
2176     }
2178     if (!$userid) {
2179         if (empty($USER->studentview)) {
2180             return (!empty($USER->student[$courseid]));
2181         } else {
2182             return(!empty($USER->teacher[$courseid]) or isadmin());
2183         }
2184     }
2186   //  $timenow = time();   // todo:  add time check below
2188     return record_exists('user_students', 'userid', $userid, 'course', $courseid);
2191 /**
2192  * Determines if the specified user is logged in as guest.
2193  *
2194  * @uses $USER
2195  * @param int $userid The user being tested. You can set this to 0 or leave it blank to test the currently logged in user.
2196  * @return bool
2197  */
2198 function isguest($userid=0) {
2199     global $USER;
2201     if (!$userid) {
2202         if (empty($USER->username)) {
2203             return false;
2204         }
2205         return ($USER->username == 'guest');
2206     }
2208     return record_exists('user', 'id', $userid, 'username', 'guest');
2211 /**
2212  * Determines if the currently logged in user is in editing mode
2213  *
2214  * @uses $USER
2215  * @param int $courseid The id of the course being tested
2216  * @param user $user A {@link $USER} object. If null then the currently logged in user is used.
2217  * @return bool
2218  */
2219 function isediting($courseid, $user=NULL) {
2220     global $USER;
2221     if (!$user) {
2222         $user = $USER;
2223     }
2224     if (empty($user->editing)) {
2225         return false;
2226     }
2227     return ($user->editing and isteacher($courseid, $user->id));
2230 /**
2231  * Determines if the logged in user is currently moving an activity
2232  *
2233  * @uses $USER
2234  * @param int $courseid The id of the course being tested
2235  * @return bool
2236  */
2237 function ismoving($courseid) {
2238     global $USER;
2240     if (!empty($USER->activitycopy)) {
2241         return ($USER->activitycopycourse == $courseid);
2242     }
2243     return false;
2246 /**
2247  * Given an object containing firstname and lastname
2248  * values, this function returns a string with the
2249  * full name of the person.
2250  * The result may depend on system settings
2251  * or language.  'override' will force both names
2252  * to be used even if system settings specify one.
2253  *
2254  * @uses $CFG
2255  * @uses $SESSION
2256  * @param object $user A {@link $USER} object to get full name of
2257  * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2258  */
2259 function fullname($user, $override=false) {
2261     global $CFG, $SESSION;
2263     if (!isset($user->firstname) and !isset($user->lastname)) {
2264         return '';
2265     }
2267     if (!$override) {
2268         if (!empty($CFG->forcefirstname)) {
2269             $user->firstname = $CFG->forcefirstname;
2270         }
2271         if (!empty($CFG->forcelastname)) {
2272             $user->lastname = $CFG->forcelastname;
2273         }
2274     }
2276     if (!empty($SESSION->fullnamedisplay)) {
2277         $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2278     }
2280     if ($CFG->fullnamedisplay == 'firstname lastname') {
2281         return $user->firstname .' '. $user->lastname;
2283     } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2284         return $user->lastname .' '. $user->firstname;
2286     } else if ($CFG->fullnamedisplay == 'firstname') {
2287         if ($override) {
2288             return get_string('fullnamedisplay', '', $user);
2289         } else {
2290             return $user->firstname;
2291         }
2292     }
2294     return get_string('fullnamedisplay', '', $user);
2297 /**
2298  * Sets a moodle cookie with an encrypted string
2299  *
2300  * @uses $CFG
2301  * @uses DAYSECS
2302  * @uses HOURSECS
2303  * @param string $thing The string to encrypt and place in a cookie
2304  */
2305 function set_moodle_cookie($thing) {
2306     global $CFG;
2308     if ($thing == 'guest') {  // Ignore guest account
2309         return;
2310     }
2312     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2314     $days = 60;
2315     $seconds = DAYSECS*$days;
2317     setCookie($cookiename, '', time() - HOURSECS, '/');
2318     setCookie($cookiename, rc4encrypt($thing), time()+$seconds, '/');
2321 /**
2322  * Gets a moodle cookie with an encrypted string
2323  *
2324  * @uses $CFG
2325  * @return string
2326  */
2327 function get_moodle_cookie() {
2328     global $CFG;
2330     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2332     if (empty($_COOKIE[$cookiename])) {
2333         return '';
2334     } else {
2335         $thing = rc4decrypt($_COOKIE[$cookiename]);
2336         return ($thing == 'guest') ? '': $thing;  // Ignore guest account
2337     }
2340 /**
2341  * Returns true if an internal authentication method is being used.
2342  * if method not specified then, global default is assumed
2343  *
2344  * @uses $CFG
2345  * @param string $auth Form of authentication required
2346  * @return bool
2347  * @todo Outline auth types and provide code example
2348  */
2349 function is_internal_auth($auth='') {
2350 /// Returns true if an internal authentication method is being used.
2351 /// If auth not specified then global default is assumed
2353     global $CFG;
2355     if (empty($auth)) {
2356         $auth = $CFG->auth;
2357     }
2359     return ($auth == "email" || $auth == "none" || $auth == "manual");
2362 /**
2363  * Returns an array of user fields
2364  *
2365  * @uses $CFG
2366  * @uses $db
2367  * @return array User field/column names
2368  */
2369 function get_user_fieldnames() {
2371     global $CFG, $db;
2373     $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2374     unset($fieldarray['ID']);
2376     return $fieldarray;
2379 /**
2380  * Creates a bare-bones user record
2381  *
2382  * @uses $CFG
2383  * @param string $username New user's username to add to record
2384  * @param string $password New user's password to add to record
2385  * @param string $auth Form of authentication required
2386  * @return object A {@link $USER} object
2387  * @todo Outline auth types and provide code example
2388  */
2389 function create_user_record($username, $password, $auth='') {
2390     global $CFG;
2392     //just in case check text case
2393     $username = trim(moodle_strtolower($username));
2395     if (function_exists('auth_get_userinfo')) {
2396         if ($newinfo = auth_get_userinfo($username)) {
2397             $newinfo = truncate_userinfo($newinfo);
2398             foreach ($newinfo as $key => $value){
2399                 $newuser->$key = addslashes(stripslashes($value)); // Just in case
2400             }
2401         }
2402     }
2404     if (!empty($newuser->email)) {
2405         if (email_is_not_allowed($newuser->email)) {
2406             unset($newuser->email);
2407         }
2408     }
2410     $newuser->auth = (empty($auth)) ? $CFG->auth : $auth;
2411     $newuser->username = $username;
2412     update_internal_user_password($newuser, $password, false);
2413     $newuser->lang = $CFG->lang;
2414     $newuser->confirmed = 1;
2415     $newuser->lastIP = getremoteaddr();
2416     $newuser->timemodified = time();
2418     if (insert_record('user', $newuser)) {
2419          $user = get_complete_user_data('username', $newuser->username);
2420          if($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'}){
2421              set_user_preference('auth_forcepasswordchange', 1, $user->id);
2422          }
2423          return $user;
2424     }
2425     return false;
2428 /**
2429  * Will update a local user record from an external source
2430  *
2431  * @uses $CFG
2432  * @param string $username New user's username to add to record
2433  * @return user A {@link $USER} object
2434  */
2435 function update_user_record($username) {
2436     global $CFG;
2438     if (function_exists('auth_get_userinfo')) {
2439         $username = trim(moodle_strtolower($username)); /// just in case check text case
2441         $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2442         $authconfig = get_config('auth/' . $oldinfo->auth);
2444         if ($newinfo = auth_get_userinfo($username)) {
2445             $newinfo = truncate_userinfo($newinfo);
2446             foreach ($newinfo as $key => $value){
2447                 $confkey = 'field_updatelocal_' . $key;
2448                 if (!empty($authconfig->$confkey) && $authconfig->$confkey === 'onlogin') {
2449                     $value = addslashes(stripslashes($value));   // Just in case
2450                     set_field('user', $key, $value, 'username', $username)
2451                         || error_log("Error updating $key for $username");
2452                 }
2453             }
2454         }
2455     }
2456     return get_complete_user_data('username', $username);
2459 function truncate_userinfo($info) {
2460 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2461 /// which may have large fields
2463     // define the limits
2464     $limit = array(
2465                     'username'    => 100,
2466                     'idnumber'    =>  64,
2467                     'firstname'   =>  20,
2468                     'lastname'    =>  20,
2469                     'email'       => 100,
2470                     'icq'         =>  15,
2471                     'phone1'      =>  20,
2472                     'phone2'      =>  20,
2473                     'institution' =>  40,
2474                     'department'  =>  30,
2475                     'address'     =>  70,
2476                     'city'        =>  20,
2477                     'country'     =>   2,
2478                     'url'         => 255,
2479                     );
2481     // apply where needed
2482     foreach (array_keys($info) as $key) {
2483         if (!empty($limit[$key])) {
2484             $info[$key] = trim(substr($info[$key],0, $limit[$key]));
2485         }
2486     }
2488     return $info;
2491 /**
2492  * Retrieve the guest user object
2493  *
2494  * @uses $CFG
2495  * @return user A {@link $USER} object
2496  */
2497 function guest_user() {
2498     global $CFG;
2500     if ($newuser = get_record('user', 'username', 'guest')) {
2501         $newuser->loggedin = true;
2502         $newuser->confirmed = 1;
2503         $newuser->site = $CFG->wwwroot;
2504         $newuser->lang = $CFG->lang;
2505         $newuser->lastIP = getremoteaddr();
2506     }
2508     return $newuser;
2511 /**
2512  * Given a username and password, this function looks them
2513  * up using the currently selected authentication mechanism,
2514  * and if the authentication is successful, it returns a
2515  * valid $user object from the 'user' table.
2516  *
2517  * Uses auth_ functions from the currently active auth module
2518  *
2519  * @uses $CFG
2520  * @param string $username  User's username
2521  * @param string $password  User's password
2522  * @return user|flase A {@link $USER} object or false if error
2523  */
2524 function authenticate_user_login($username, $password) {
2526     global $CFG;
2528     // First try to find the user in the database
2530     if (!$user = get_complete_user_data('username', $username)) {
2531         $user->id = 0;     // Not a user
2532         $user->auth = $CFG->auth;
2533     }
2535     // Sort out the authentication method we are using.
2537     if (empty($CFG->auth)) {
2538         $CFG->auth = 'manual';     // Default authentication module
2539     }
2541     if (empty($user->auth)) {      // For some reason it isn't set yet
2542         if (!empty($user->id) && (isadmin($user->id) || isguest($user->id))) {
2543             $auth = 'manual';    // Always assume these guys are internal
2544         } else {
2545             $auth = $CFG->auth;  // Normal users default to site method
2546         }
2547         // update user record from external DB
2548         if ($user->auth != 'manual' && $user->auth != 'email') {
2549             $user = update_user_record($username);
2550         }
2551     } else {
2552         $auth = $user->auth;
2553     }
2555     if (detect_munged_arguments($auth, 0)) {   // For safety on the next require
2556         return false;
2557     }
2559     if (!file_exists($CFG->dirroot .'/auth/'. $auth .'/lib.php')) {
2560         $auth = 'manual';    // Can't find auth module, default to internal
2561     }
2563     require_once($CFG->dirroot .'/auth/'. $auth .'/lib.php');
2565     if (auth_user_login($username, $password)) {  // Successful authentication
2566         if ($user->id) {                          // User already exists in database
2567             if (empty($user->auth)) {             // For some reason auth isn't set yet
2568                 set_field('user', 'auth', $auth, 'username', $username);
2569             }
2570             update_internal_user_password($user, $password);
2571             if (!is_internal_auth()) {            // update user record from external DB
2572                 $user = update_user_record($username);
2573             }
2574         } else {
2575             $user = create_user_record($username, $password, $auth);
2576         }
2578         if (function_exists('auth_iscreator')) {    // Check if the user is a creator
2579             $useriscreator = auth_iscreator($username);
2580             if (!is_null($useriscreator)) {
2581                 if ($useriscreator) {
2582                     if (! record_exists('user_coursecreators', 'userid', $user->id)) {
2583                         $cdata->userid = $user->id;
2584                         if (! insert_record('user_coursecreators', $cdata)) {
2585                             error('Cannot add user to course creators.');
2586                         }
2587                     }
2588                 } else {
2589                     if (record_exists('user_coursecreators', 'userid', $user->id)) {
2590                         if (! delete_records('user_coursecreators', 'userid', $user->id)) {
2591                             error('Cannot remove user from course creators.');
2592                         }
2593                     }
2594                 }
2595             }
2596         }
2598     /// Log in to a second system if necessary
2599         if (!empty($CFG->sso)) {
2600             include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
2601             if (function_exists('sso_user_login')) {
2602                 if (!sso_user_login($username, $password)) {   // Perform the signon process
2603                     notify('Second sign-on failed');
2604                 }
2605             }
2606         }
2608         return $user;
2610     } else {
2611         add_to_log(0, 'login', 'error', 'index.php', $username);
2612         error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Failed Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
2613         return false;
2614     }
2617 /**
2618  * Compare password against hash stored in local user table.
2619  * If necessary it also updates the stored hash to new format.
2620  * 
2621  * @param object user
2622  * @param string plain text password
2623  * @return bool is password valid?
2624  */
2625 function validate_internal_user_password(&$user, $password) {
2626     global $CFG;
2628     $validated = false;
2630     if (!empty($CFG->unicodedb)) {
2631         $textlib = textlib_get_instance();
2632         $convpassword = $textlib->convert($password, 'UTF-8', get_string('oldcharset'));
2633     } else {
2634         $convpassword = false;
2635     }
2637     if ($user->password == md5($password)) {
2638         $validated = true;
2639     } elseif ($convpassword !== false && $user->password == md5($convpassword)) {
2640         $validated = true;
2641     }
2643     if ($validated) {
2644         update_internal_user_password($user, $password);
2645     }
2647     return $validated;
2650 /**
2651  * Calculate hashed value from password using current hash mechanism.
2652  * This mechanism might change in future, older methodes are handled in validate_internal_user_password()
2653  * 
2654  * @param string password
2655  * @return string password hash
2656  */
2657 function hash_internal_user_password($password) {
2658     return md5($password);
2661 /**
2662  * Update pssword hash in user object.
2663  * 
2664  * @param object user
2665  * @param string plain text password
2666  * @param bool store changes also in db, default true
2667  * @return true if hash changed
2668  */
2669 function update_internal_user_password(&$user, $password, $storeindb=true) {
2670     global $CFG;
2672     if (!empty($CFG->{$user->auth.'_preventpassindb'})) {
2673         $hashedpassword = 'not cached';
2674     } else {
2675         $hashedpassword = hash_internal_user_password($password);
2676     }
2678     if ($user->password != $hashedpassword) {
2679         if ($storeindb) {
2680             if (!set_field('user', 'password',  $hashedpassword, 'username', $user->username)) {
2681                 return false;
2682             }
2683         }
2684         $user->password = $hashedpassword;
2685     }
2686     return true;
2689 /**
2690  * Get a complete user record, which includes all the info
2691  * in the user record, as well as membership information
2692  * Intended for setting as $USER session variable
2693  *
2694  * @uses $CFG
2695  * @uses SITEID
2696  * @param string $field The user field to be checked for a given value.
2697  * @param string $value The value to match for $field.
2698  * @return user A {@link $USER} object.
2699  */
2700 function get_complete_user_data($field, $value) {
2702     global $CFG;
2704     if (!$field || !$value) {
2705         return false;
2706     }
2708 /// Get all the basic user data
2710     if (! $user = get_record_select('user', $field .' = \''. $value .'\' AND deleted <> \'1\'')) {
2711         return false;
2712     }
2714 /// Add membership information
2716     if ($admins = get_records('user_admins', 'userid', $user->id)) {
2717         $user->admin = true;
2718     }
2720     $user->student[SITEID] = isstudent(SITEID, $user->id);
2722 /// Load the list of enrolment plugin enabled
2724     if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) {
2725         $plugins = array($CFG->enrol);
2726     }
2727     require_once($CFG->dirroot .'/enrol/enrol.class.php');
2728     foreach ($plugins as $p) {
2729         $enrol = enrolment_factory::factory($p);
2730         if (method_exists($enrol, 'get_student_courses')) {
2731             $enrol->get_student_courses($user);
2732         }
2733         if (method_exists($enrol, 'get_teacher_courses')) {
2734             $enrol->get_teacher_courses($user);
2735         }
2736         unset($enrol);
2737     }
2739 /// Get various settings and preferences
2741     if ($displays = get_records('course_display', 'userid', $user->id)) {
2742         foreach ($displays as $display) {
2743             $user->display[$display->course] = $display->display;
2744         }
2745     }
2747     if ($preferences = get_records('user_preferences', 'userid', $user->id)) {
2748         foreach ($preferences as $preference) {
2749             $user->preference[$preference->name] = $preference->value;
2750         }
2751     }
2753     if ($groups = get_records('groups_members', 'userid', $user->id)) {
2754         foreach ($groups as $groupmember) {
2755             $courseid = get_field('groups', 'courseid', 'id', $groupmember->groupid);
2756             //change this to 2D array so we can put multiple groups in a course
2757             $user->groupmember[$courseid][] = $groupmember->groupid;
2758         }
2759     }
2761 /// Rewrite some variables if necessary
2762     if (!empty($user->description)) {
2763         $user->description = true;   // No need to cart all of it around
2764     }
2765     if ($user->username == 'guest') {
2766         $user->lang       = $CFG->lang;               // Guest language always same as site
2767         $user->firstname  = get_string('guestuser');  // Name always in current language
2768         $user->lastname   = ' ';
2769     }
2771     $user->loggedin = true;
2772     $user->site     = $CFG->wwwroot; // for added security, store the site in the session
2773     $user->sesskey  = random_string(10);
2774     $user->sessionIP = md5(getremoteaddr());   // Store the current IP in the session
2776     return $user;
2780 function get_user_info_from_db($field, $value) {  // For backward compatibility
2781     return get_complete_user_data($field, $value);
2784 /*
2785  * When logging in, this function is run to set certain preferences
2786  * for the current SESSION
2787  */
2788 function set_login_session_preferences() {
2789     global $SESSION, $CFG;
2791     $SESSION->justloggedin = true;
2793     unset($SESSION->lang);
2795     // Restore the calendar filters, if saved
2796     if (intval(get_user_preferences('calendar_persistflt', 0))) {
2797         include_once($CFG->dirroot.'/calendar/lib.php');
2798         calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
2799     }
2803 /**
2804  * Enrols (or re-enrols) a student in a given course
2805  *
2806  * NOTE: Defaults to 'manual' enrolment - enrolment plugins
2807  * must set it explicitly.
2808  *
2809  * @uses $CFG
2810  * @param int $userid The id of the user that is being tested against. Set this to 0 if you would just like to test against the currently logged in user.
2811  * @param int $courseid The id of the course that is being viewed
2812  * @param int $timestart ?
2813  * @param int $timeend ?
2814  * @param string $enrol ?
2815  * @return bool
2816  * @todo Finish documenting this function
2817  */
2818 function enrol_student($userid, $courseid, $timestart=0, $timeend=0, $enrol='manual') {
2820     global $CFG, $USER;
2822     if (!$course = get_record('course', 'id', $courseid)) {  // Check course
2823         return false;
2824     }
2825     if (!$user = get_record('user', 'id', $userid)) {        // Check user
2826         return false;
2827     }
2828     // enrol the student in any parent meta courses...
2829     if ($parents = get_records('course_meta', 'child_course', $courseid)) {
2830         foreach ($parents as $parent) {
2831             enrol_student($userid, $parent->parent_course, $timestart, $timeend,'metacourse');
2832             // if we're enrolling ourselves in the child course, add the parent courses to USER too
2833             // otherwise they'll have to logout and in again to get it
2834             // http://moodle.org/mod/forum/post.php?reply=185699
2835             if (!empty($USER) && $userid == $USER->id) {
2836                 $USER->student[$parent->parent_course] = true;
2837             }
2838         }
2839     }
2841     if ($student = get_record('user_students', 'userid', $userid, 'course', $courseid)) {
2842         $student->timestart = $timestart;
2843         $student->timeend = $timeend;
2844         $student->time = time();
2845         $student->enrol = $enrol;
2846         return update_record('user_students', $student);
2848     } else {
2849         require_once("$CFG->dirroot/mod/forum/lib.php");
2850         forum_add_user($userid, $courseid);
2852         $student->userid = $userid;
2853         $student->course = $courseid;
2854         $student->timestart = $timestart;
2855         $student->timeend = $timeend;
2856         $student->time = time();
2857         $student->enrol = $enrol;
2858         return insert_record('user_students', $student);
2859     }
2862 /**
2863  * Unenrols a student from a given course
2864  *
2865  * @param int $courseid The id of the course that is being viewed, if any
2866  * @param int $userid The id of the user that is being tested against.
2867  * @return bool
2868  */
2869 function unenrol_student($userid, $courseid=0) {
2870     global $CFG;
2872     if ($courseid) {
2873         /// First delete any crucial stuff that might still send mail
2874         if ($forums = get_records('forum', 'course', $courseid)) {
2875             foreach ($forums as $forum) {
2876                 delete_records('forum_subscriptions', 'forum', $forum->id, 'userid', $userid);
2877             }
2878         }
2879         if ($groups = get_groups($courseid, $userid)) {
2880             foreach ($groups as $group) {
2881                 delete_records('groups_members', 'groupid', $group->id, 'userid', $userid);
2882             }
2883         }
2884         // unenrol the student from any parent meta courses...
2885         if ($parents = get_records('course_meta','child_course',$courseid)) {
2886             foreach ($parents as $parent) {
2887                 if (!record_exists_sql('SELECT us.id FROM '.$CFG->prefix.'user_students us, '
2888                                        .$CFG->prefix.'course_meta cm WHERE cm.child_course = us.course
2889                                         AND us.userid = '.$userid .' AND us.course != '.$courseid)) {
2890                     unenrol_student($userid, $parent->parent_course);
2891                 }
2892             }
2893         }
2894         return delete_records('user_students', 'userid', $userid, 'course', $courseid);
2896     } else {
2897         delete_records('forum_subscriptions', 'userid', $userid);
2898         delete_records('groups_members', 'userid', $userid);
2899         return delete_records('user_students', 'userid', $userid);
2900     }
2903 /**
2904  * Add a teacher to a given course
2905  *
2906  * @uses $USER
2907  * @param int $userid The id of the user that is being tested against. Set this to 0 if you would just like to test against the currently logged in user.
2908  * @param int $courseid The id of the course that is being viewed, if any
2909  * @param int $editall ?
2910  * @param string $role ?
2911  * @param int $timestart ?
2912  * @param int $timeend ?
2913  * @param string $enrol ?
2914  * @return bool
2915  * @todo Finish documenting this function
2916  */
2917 function add_teacher($userid, $courseid, $editall=1, $role='', $timestart=0, $timeend=0, $enrol='manual') {
2918     global $CFG;
2920     if ($teacher = get_record('user_teachers', 'userid', $userid, 'course', $courseid)) {
2921         $newteacher = NULL;
2922         $newteacher->id = $teacher->id;
2923         $newteacher->editall = $editall;
2924         $newteacher->enrol = $enrol;
2925         if ($role) {
2926             $newteacher->role = $role;
2927         }
2928         if ($timestart) {
2929             $newteacher->timestart = $timestart;
2930         }
2931         if ($timeend) {
2932             $newteacher->timeend = $timeend;
2933         }
2934         return update_record('user_teachers', $newteacher);
2935     }
2937     if (!record_exists('user', 'id', $userid)) {
2938         return false;   // no such user
2939     }
2941     if (!record_exists('course', 'id', $courseid)) {
2942         return false;   // no such course
2943     }
2945     $teacher = NULL;
2946     $teacher->userid  = $userid;
2947     $teacher->course  = $courseid;
2948     $teacher->editall = $editall;
2949     $teacher->role    = $role;
2950     $teacher->enrol   = $enrol;
2951     $teacher->timemodified = time();
2952     $teacher->timestart = $timestart;
2953     $teacher->timeend = $timeend;
2954     if ($student = get_record('user_students', 'userid', $userid, 'course', $courseid)) {
2955         $teacher->timestart = $student->timestart;
2956         $teacher->timeend = $student->timeend;
2957         $teacher->timeaccess = $student->timeaccess;
2958     }
2960     if (record_exists('user_teachers', 'course', $courseid)) {
2961         $teacher->authority = 2;
2962     } else {
2963         $teacher->authority = 1;
2964     }
2965     delete_records('user_students', 'userid', $userid, 'course', $courseid); // Unenrol as student
2967     /// Add forum subscriptions for new users
2968     require_once($CFG->dirroot.'/mod/forum/lib.php');
2969     forum_add_user($userid, $courseid);
2971     return insert_record('user_teachers', $teacher);
2975 /**
2976  * Removes a teacher from a given course (or ALL courses)
2977  * Does not delete the user account
2978  *
2979  * @param int $courseid The id of the course that is being viewed, if any
2980  * @param int $userid The id of the user that is being tested against.
2981  * @return bool
2982  */
2983 function remove_teacher($userid, $courseid=0) {
2984     if ($courseid) {
2985         /// First delete any crucial stuff that might still send mail
2986         if ($forums = get_records('forum', 'course', $courseid)) {
2987             foreach ($forums as $forum) {
2988                 delete_records('forum_subscriptions', 'forum', $forum->id, 'userid', $userid);
2989             }
2990         }
2992         /// Next if the teacher is not registered as a student, but is
2993         /// a member of a group, remove them from the group.
2994         if (!isstudent($courseid, $userid)) {
2995             if ($groups = get_groups($courseid, $userid)) {
2996                 foreach ($groups as $group) {
2997                     delete_records('groups_members', 'groupid', $group->id, 'userid', $userid);
2998                 }
2999             }
3000         }
3002         return delete_records('user_teachers', 'userid', $userid, 'course', $courseid);
3003     } else {
3004         delete_records('forum_subscriptions', 'userid', $userid);
3005         return delete_records('user_teachers', 'userid', $userid);
3006     }
3009 /**
3010  * Add a creator to the site
3011  *
3012  * @param int $userid The id of the user that is being tested against.
3013  * @return bool
3014  */
3015 function add_creator($userid) {
3017     if (!record_exists('user_admins', 'userid', $userid)) {
3018         if (record_exists('user', 'id', $userid)) {
3019             $creator->userid = $userid;
3020             return insert_record('user_coursecreators', $creator);
3021         }
3022         return false;
3023     }
3024     return true;
3027 /**
3028  * Remove a creator from a site
3029  *
3030  * @uses $db
3031  * @param int $userid The id of the user that is being tested against.
3032  * @return bool
3033  */
3034 function remove_creator($userid) {
3035     global $db;
3037     return delete_records('user_coursecreators', 'userid', $userid);
3040 /**
3041  * Add an admin to a site
3042  *
3043  * @uses SITEID
3044  * @param int $userid The id of the user that is being tested against.
3045  * @return bool
3046  */
3047 function add_admin($userid) {
3049     if (!record_exists('user_admins', 'userid', $userid)) {
3050         if (record_exists('user', 'id', $userid)) {
3051             $admin->userid = $userid;
3053             // any admin is also a teacher on the site course
3054             if (!record_exists('user_teachers', 'course', SITEID, 'userid', $userid)) {
3055                 if (!add_teacher($userid, SITEID)) {
3056                     return false;
3057                 }
3058             }
3060             return insert_record('user_admins', $admin);
3061         }
3062         return false;
3063     }
3064     return true;
3067 /**
3068  * Removes an admin from a site
3069  *
3070  * @uses $db
3071  * @uses SITEID
3072  * @param int $userid The id of the user that is being tested against.
3073  * @return bool
3074  */
3075 function remove_admin($userid) {
3076     global $db;
3078     // remove also from the list of site teachers
3079     remove_teacher($userid, SITEID);
3081     return delete_records('user_admins', 'userid', $userid);
3084 /**
3085  * Delete a course, including all related data from the database,
3086  * and any associated files from the moodledata folder.
3087  *
3088  * @param int $courseid The id of the course to delete.
3089  * @param bool $showfeedback Whether to display notifications of each action the function performs.
3090  * @return bool true if all the removals succeeded. false if there were any failures. If this
3091  *             method returns false, some of the removals will probably have succeeded, and others
3092  *             failed, but you have no way of knowing which.
3093  */
3094 function delete_course($courseid, $showfeedback = true) {
3095     global $CFG;
3096     $result = true;
3098     if (!remove_course_contents($courseid, $showfeedback)) {
3099         if ($showfeedback) {
3100             notify("An error occurred while deleting some of the course contents.");
3101         }
3102         $result = false;
3103     }
3105     if (!delete_records("course", "id", $courseid)) {
3106         if ($showfeedback) {
3107             notify("An error occurred while deleting the main course record.");
3108         }
3109         $result = false;
3110     }
3112     if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3113         if ($showfeedback) {
3114             notify("An error occurred while deleting the course files.");
3115         }
3116         $result = false;
3117     }
3119     return $result;
3122 /**
3123  * Clear a course out completely, deleting all content
3124  * but don't delete the course itself
3125  *
3126  * @uses $CFG
3127  * @param int $courseid The id of the course that is being deleted
3128  * @param bool $showfeedback Whether to display notifications of each action the function performs.
3129  * @return bool true if all the removals succeeded. false if there were any failures. If this
3130  *             method returns false, some of the removals will probably have succeeded, and others
3131  *             failed, but you have no way of knowing which.
3132  */
3133 function remove_course_contents($courseid, $showfeedback=true) {
3135     global $CFG;
3137     $result = true;
3139     if (! $course = get_record('course', 'id', $courseid)) {
3140         error('Course ID was incorrect (can\'t find it)');
3141     }
3143     $strdeleted = get_string('deleted');
3145     // First delete every instance of every module
3147     if ($allmods = get_records('modules') ) {
3148         foreach ($allmods as $mod) {
3149             $modname = $mod->name;
3150             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3151             $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance
3152             $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon)
3153             $count=0;
3154             if (file_exists($modfile)) {
3155                 include_once($modfile);
3156                 if (function_exists($moddelete)) {
3157                     if ($instances = get_records($modname, 'course', $course->id)) {
3158                         foreach ($instances as $instance) {
3159                             if ($moddelete($instance->id)) {
3160                                 $count++;
3161                             } else {
3162                                 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
3163                                 $result = false;
3164                             }
3165                         }
3166                     }
3167                 } else {
3168                     notify('Function '. $moddelete() .'doesn\'t exist!');
3169                     $result = false;
3170                 }
3172                 if (function_exists($moddeletecourse)) {
3173                     $moddeletecourse($course);
3174                 }
3175             }
3176             if ($showfeedback) {
3177                 notify($strdeleted .' '. $count .' x '. $modname);
3178             }
3179         }
3180     } else {
3181         error('No modules are installed!');
3182     }
3184     // Give local code a chance to delete its references to this course.
3185     require_once('locallib.php');
3186     notify_local_delete_course($courseid, $showfeedback);
3188     // Delete course blocks
3189     if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3190         if ($showfeedback) {
3191             notify($strdeleted .' block_instance');
3192         }
3193     } else {
3194         $result = false;
3195     }
3197     // Delete Other stuff.
3198     // This array stores the tables that need to be cleared, as
3199     // table_name => column_name that contains the course id.
3200     $tablestoclear = array(
3201         'user_students' => 'course', // Delete any user stuff
3202         'user_teachers' => 'course',
3203         'event' => 'courseid', // Delete events
3204         'log' => 'course', // Delete logs
3205         'course_sections' => 'course', // Delete any course stuff
3206         'course_modules' => 'course',
3207         'grade_category' => 'courseid', // Delete gradebook stuff
3208         'grade_exceptions' => 'courseid',
3209         'grade_item' => 'courseid',
3210         'grade_letter' => 'courseid',
3211         'grade_preferences' => 'courseid'
3212     );
3213     foreach ($tablestoclear as $table => $col) {
3214         if (delete_records($table, $col, $course->id)) {
3215             if ($showfeedback) {
3216                 notify($strdeleted . ' ' . $table);
3217             }
3218         } else {
3219             $result = false;
3220         }
3221     }
3223     // Delete any groups
3224     if ($groups = get_records('groups', 'courseid', $course->id)) {
3225         foreach ($groups as $group) {
3226             if (delete_records('groups_members', 'groupid', $group->id)) {
3227                 if ($showfeedback) {
3228                     notify($strdeleted .' groups_members');
3229                 }
3230             } else {
3231                 $result = false;
3232             }
3233             if (delete_records('groups', 'id', $group->id)) {
3234                 if ($showfeedback) {
3235                     notify($strdeleted .' groups');
3236                 }
3237             } else {
3238                 $result = false;
3239             }
3240         }
3241     }
3243     if ($course->metacourse) {
3244         delete_records("course_meta","parent_course",$course->id);
3245         sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3246         if ($showfeedback) {
3247             notify("$strdeleted course_meta");
3248         }
3249     } else {
3250         if ($parents = get_records("course_meta","child_course",$course->id)) {
3251             foreach ($parents as $parent) {
3252                 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3253             }
3254             if ($showfeedback) {
3255                 notify("$strdeleted course_meta");
3256             }
3257         }
3258     }
3260     return $result;
3264 /**
3265  * This function will empty a course of user data as much as
3266  * possible. It will retain the activities and the structure
3267  * of the course.
3268  *
3269  * @uses $CFG
3270  * @param int $courseid The id of the course that is being emptied of user data.
3271  * @param bool $showfeedback Whether to display notifications of each action the function performs.
3272  * @param bool $removestudents Whether to remove matching records from the user_students and groups_members table.
3273  * @param bool $removeteachers Whether to remove matching records from the user_teachers table.
3274  * @param bool $removegroups Whether to remove matching records from the groups table.
3275  * @param bool $removeevents Whether to remove matching records from the event table.
3276  * @param bool $removelogs Whether to remove matching records from the log table.
3277  * @return bool true if all the removals succeeded. false if there were any failures. If this
3278  *             method returns false, some of the removals will probably have succeeded, and others
3279  *             failed, but you have no way of knowing which.
3280  */
3281 function remove_course_userdata($courseid, $showfeedback=true,
3282                                 $removestudents=true, $removeteachers=false, $removegroups=true,
3283                                 $removeevents=true, $removelogs=false) {
3285     global $CFG;
3287     $result = true;
3289     if (! $course = get_record('course', 'id', $courseid)) {
3290         error('Course ID was incorrect (can\'t find it)');
3291     }
3293     $strdeleted = get_string('deleted');
3295     // Look in every instance of every module for data to delete
3297     if ($allmods = get_records('modules') ) {
3298         foreach ($allmods as $mod) {
3299             $modname = $mod->name;
3300             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3301             $moddeleteuserdata = $modname .'_delete_userdata';   // Function to delete user data
3302             $count=0;
3303             if (file_exists($modfile)) {
3304                 @include_once($modfile);
3305                 if (function_exists($moddeleteuserdata)) {
3306                     $moddeleteuserdata($course, $showfeedback);
3307                 }
3308             }
3309         }
3310     } else {
3311         error('No modules are installed!');
3312     }
3314     // Delete other stuff
3316     if ($removestudents) {
3317         /// Delete student enrolments
3318         if (delete_records('user_students', 'course', $course->id)) {
3319             if ($showfeedback) {
3320                 notify($strdeleted .' user_students');
3321             }
3322         } else {
3323             $result = false;
3324         }
3325         /// Delete group members (but keep the groups)
3326         if ($groups = get_records('groups', 'courseid', $course->id)) {
3327             foreach ($groups as $group) {
3328                 if (delete_records('groups_members', 'groupid', $group->id)) {
3329                     if ($showfeedback) {
3330                         notify($strdeleted .' groups_members');
3331                     }
3332                 } else {
3333                     $result = false;
3334                 }
3335             }
3336         }
3337     }
3339     if ($removeteachers) {
3340         if (delete_records('user_teachers', 'course', $course->id)) {
3341             if ($showfeedback) {
3342                 notify($strdeleted .' user_teachers');
3343             }
3344         } else {
3345             $result = false;
3346         }
3347     }
3349     if ($removegroups) {
3350         if ($groups = get_records('groups', 'courseid', $course->id)) {
3351             foreach ($groups as $group) {
3352                 if (delete_records('groups', 'id', $group->id)) {
3353                     if ($showfeedback) {
3354                         notify($strdeleted .' groups');
3355                     }
3356                 } else {
3357                     $result = false;
3358                 }
3359             }
3360         }
3361     }
3363     if ($removeevents) {
3364         if (delete_records('event', 'courseid', $course->id)) {
3365             if ($showfeedback) {
3366                 notify($strdeleted .' event');
3367             }
3368         } else {
3369             $result = false;
3370         }
3371     }
3373     if ($removelogs) {
3374         if (delete_records('log', 'course', $course->id)) {
3375             if ($showfeedback) {
3376                 notify($strdeleted .' log');
3377             }
3378         } else {
3379             $result = false;
3380         }
3381     }
3383     return $result;
3387 /// GROUPS /////////////////////////////////////////////////////////
3390 /**
3391  * Determines if the user a member of the given group
3392  *
3393  * @uses $USER
3394  * @param int $groupid The group to check the membership of
3395  * @param int $userid The user to check against the group
3396  * @return bool
3397  */
3398 function ismember($groupid, $userid=0) {
3399     global $USER;
3401     if (!$groupid) {   // No point doing further checks
3402         return false;
3403     }
3404     //if groupid is supplied in array format
3405     if (!$userid) {
3406         if (empty($USER->groupmember)) {
3407             return false;
3408         }
3409         //changed too for multiple groups
3410         foreach ($USER->groupmember as $courseid => $mgroupid) {
3411             //need to loop one more time...
3412             if (is_array($mgroupid)) {
3413                 foreach ($mgroupid as $index => $mygroupid) {
3414                     if ($mygroupid == $groupid) {
3415                         return true;
3416                     }
3417                 }
3418             } else if ($mygroupid == $groupid) {
3419                 return true;
3420             }
3421         }
3422         return false;
3423     }
3425     if (is_array($groupid)){
3426         foreach ($groupid as $index => $val){
3427             if (record_exists('groups_members', 'groupid', $val, 'userid', $userid)){
3428                 return true;
3429             }
3430         }
3431     }
3432     else {
3433         return record_exists('groups_members', 'groupid', $groupid, 'userid', $userid);
3434     }
3435     return false;
3437     //else group id is in single format
3439     //return record_exists('groups_members', 'groupid', $groupid, 'userid', $userid);
3442 /**
3443  * Add a user to a group, return true upon success or if user already a group member
3444  *
3445  * @param int $groupid  The group id to add user to
3446  * @param int $userid   The user id to add to the group
3447  * @return bool
3448  */
3449 function add_user_to_group ($groupid, $userid) {
3450     if (ismember($groupid, $userid)) return true;
3451     $record->groupid = $groupid;
3452     $record->userid = $userid;
3453     $record->timeadded = time();
3454     return (insert_record('groups_members', $record) !== false);
3458 /**
3459  * Get the group ID of the current user in the given course
3460  *
3461  * @uses $USER
3462  * @param int $courseid The course being examined - relates to id field in 'course' table.
3463  * @return int
3464  */
3465 function mygroupid($courseid) {
3466     global $USER;
3467     if (empty($USER->groupmember[$courseid])) {
3468         return 0;
3469     } else {
3470         //this is an array of ids >.<
3471         return $USER->groupmember[$courseid];
3472     }
3475 /**
3476  * For a given course, and possibly course module, determine
3477  * what the current default groupmode is:
3478  * NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3479  *
3480  * @param course $course A {@link $COURSE} object
3481  * @param object $cm A course module object
3482  * @return int A group mode (NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS)
3483  */
3484 function groupmode($course, $cm=null) {
3486     if ($cm and !$course->groupmodeforce) {
3487         return $cm->groupmode;
3488     }
3489     return $course->groupmode;
3493 /**
3494  * Sets the current group in the session variable
3495  *
3496  * @uses $SESSION
3497  * @param int $courseid The course being examined - relates to id field in 'course' table.
3498  * @param int $groupid The group being examined.
3499  * @return int Current group id which was set by this function
3500  */
3501 function set_current_group($courseid, $groupid) {
3502     global $SESSION;
3504     return $SESSION->currentgroup[$courseid] = $groupid;
3508 /**
3509  * Gets the current group for the current user as an id or an object
3510  *
3511  * @uses $USER
3512  * @uses $SESSION
3513  * @param int $courseid The course being examined - relates to id field in 'course' table.
3514  * @param bool $full If true, the return value is a full record object. If false, just the id of the record.
3515  */
3516 function get_current_group($courseid, $full=false) {
3517     global $SESSION, $USER;
3519     if (!isset($SESSION->currentgroup[$courseid])) {
3520         if (empty($USER->groupmember[$courseid]) or isteacheredit($courseid)) {
3522             return 0;
3523         } else {
3524             //trying to add a hack >.<, always first select the first one in list
3525             $SESSION->currentgroup[$courseid] = $USER->groupmember[$courseid][0];
3526         }
3527     }
3529     if ($full) {
3530         return get_record('groups', 'id', $SESSION->currentgroup[$courseid]);
3531     } else {
3532         return $SESSION->currentgroup[$courseid];
3533     }
3536 /**
3537  * A combination function to make it easier for modules
3538  * to set up groups.
3539  *
3540  * It will use a given "groupid" parameter and try to use
3541  * that to reset the current group for the user.
3542  *
3543  * @uses VISIBLEGROUPS
3544  * @param course $course A {@link $COURSE} object
3545  * @param int $groupmode Either NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3546  * @param int $groupid Will try to use this optional parameter to
3547  *            reset the current group for the user
3548  * @return int|false Returns the current group id or false if error.
3549  */
3550 function get_and_set_current_group($course, $groupmode, $groupid=-1) {
3552     if (!$groupmode) {   // Groups don't even apply
3553         return false;
3554     }
3556     $currentgroupid = get_current_group($course->id);
3558     if ($groupid < 0) {  // No change was specified
3559         return $currentgroupid;
3560     }
3562     if ($groupid) {      // Try to change the current group to this groupid
3563         if ($group = get_record('groups', 'id', $groupid, 'courseid', $course->id)) { // Exists
3564             if (isteacheredit($course->id)) {          // Sets current default group
3565                 $currentgroupid = set_current_group($course->id, $group->id);
3567             } else if ($groupmode == VISIBLEGROUPS) {
3568                   // All groups are visible
3569                 //if (ismember($group->id)){
3570                     $currentgroupid = set_current_group($course->id, $group->id);//set this since he might post
3571                 /*)}else {
3572                     $currentgroupid = $group->id;*/
3573             } else if ($groupmode == SEPARATEGROUPS) { // student in separate groups switching
3574                 if (ismember($group->id)){//check if is a member
3575                     $currentgroupid = set_current_group($course->id, $group->id); //might need to set_current_group?
3576                 }
3577                 else {
3578                     echo ($group->id);
3579                     notify('you do not belong to this group!',error);
3580                 }
3581             }
3582         }
3583     } else {             // When groupid = 0 it means show ALL groups
3584         //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)
3585         if (isteacheredit($course->id) OR (isteacher($course->id) AND ($groupmode == VISIBLEGROUPS))) {          // Sets current default group
3586             $currentgroupid = set_current_group($course->id, 0);
3588         } else if ($groupmode == VISIBLEGROUPS) {  // All groups are visible
3589             $currentgroupid = 0;
3590         }
3591     }
3593     return $currentgroupid;
3597 /**
3598  * A big combination function to make it easier for modules
3599  * to set up groups.
3600  *
3601  * Terminates if the current user shouldn't be looking at this group
3602  * Otherwise returns the current group if there is one
3603  * Otherwise returns false if groups aren't relevant
3604  *
3605  * @uses SEPARATEGROUPS
3606  * @uses VISIBLEGROUPS
3607  * @param course $course A {@link $COURSE} object
3608  * @param int $groupmode Either NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3609  * @param string $urlroot ?
3610  * @return int|false
3611  */
3612 function setup_and_print_groups($course, $groupmode, $urlroot) {
3614     global $USER, $SESSION; //needs his id, need to hack his groups in session
3616     if (isset($_GET['group'])) {
3617         $changegroup = $_GET['group'];  /// 0 or higher
3618     } else {
3619         $changegroup = -1;              /// This means no group change was specified
3620     }
3622     $currentgroup = get_and_set_current_group($course, $groupmode, $changegroup);
3623     if ($currentgroup === false) {
3624         return false;
3625     }
3627     if ($groupmode == SEPARATEGROUPS and !isteacheredit($course->id) and !$currentgroup) {
3628         //we are in separate groups and the current group is group 0, as last set.
3629         //this can mean that either, this guy has no group
3630         //or, this guy just came from a visible all forum, and he left when he set his current group to 0 (show all)
3632         //for the second situation, we need to perform the trick and get him a group.
3633         $courseid = $course->id;
3634         if (!empty($USER->groupmember[$courseid])){
3635             $currentgroup = get_and_set_current_group($course, $groupmode, $USER->groupmember[$courseid][0]);
3636         }
3637         else {//else he has no group in this course
3638             print_heading(get_string('notingroup'));
3639             print_footer($course);
3640             exit;
3641         }
3642     }
3644     if ($groupmode == VISIBLEGROUPS or ($groupmode and isteacheredit($course->id))) {
3645         if ($groups = get_records_menu('groups', 'courseid', $course->id, 'name ASC', 'id,name')) {
3646             echo '<div align="center">';
3647             print_group_menu($groups, $groupmode, $currentgroup, $urlroot);
3648             echo '</div>';
3649         }
3650     }//added code here to allow non-editting teacher to swap in-between his own groups
3651     //added code for students in separategrous to swtich groups
3652     else if ($groupmode == SEPARATEGROUPS and (isteacher($course->id) or isstudent($course->id))) {
3653         $validgroups = array();
3654         //get all the groups this guy is in in this course
3655         if ($p = user_group($course->id,$USER->id)){
3656             //extract the name and id for the group
3657             foreach ($p as $index => $object){
3658                 $validgroups[$object->id] = $object->name;
3659             }
3660             echo '<div align="center">';
3661             //print them in the menu
3662             print_group_menu($validgroups, $groupmode, $currentgroup, $urlroot,0);
3663             echo '</div>';
3664         }
3665     }
3667     return $currentgroup;
3670 function generate_email_processing_address($modid,$modargs) {
3671     global $CFG;
3673     if (empty($CFG->siteidentifier)) {    // Unique site identification code
3674         set_config('siteidentifier', random_string(32));
3675     }
3677     $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3678     return $header . substr(md5($header.$CFG->siteidentifier),0,16).'@'.$CFG->maildomain;
3682 function moodle_process_email($modargs,$body) {
3683     // the first char should be an unencoded letter. We'll take this as an action
3684     switch ($modargs{0}) {
3685         case 'B': { // bounce
3686             list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3687             if ($user = get_record_select("user","id=$userid","id,email")) {
3688                 // check the half md5 of their email
3689                 $md5check = substr(md5($user->email),0,16);
3690                 if ($md5check == substr($modargs, -16)) {
3691                     set_bounce_count($user);
3692                 }
3693                 // else maybe they've already changed it?
3694             }
3695         }
3696         break;
3697         // maybe more later?
3698     }
3701 /// CORRESPONDENCE  ////////////////////////////////////////////////
3703 /**
3704  * Send an email to a specified user
3705  *
3706  * @uses $CFG
3707  * @uses $FULLME
3708  * @uses SITEID
3709  * @param user $user  A {@link $USER} object
3710  * @param user $from A {@link $USER} object
3711  * @param string $subject plain text subject line of the email
3712  * @param string $messagetext plain text version of the message
3713  * @param string $messagehtml complete html version of the message (optional)
3714  * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
3715  * @param string $attachname the name of the file (extension indicates MIME)
3716  * @param bool $usetrueaddress determines whether $from email address should
3717  *          be sent out. Will be overruled by user profile setting for maildisplay
3718  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3719  *          was blocked by user and "false" if there was another sort of error.
3720  */
3721 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $repyto='', $replytoname='') {
3723     global $CFG, $FULLME;
3725     global $course;                // This is a bit of an ugly hack to be gotten rid of later
3726     if (!empty($course->lang)) {   // Course language is defined
3727         $CFG->courselang = $course->lang;
3728     }
3729     if (!empty($course->theme)) {   // Course theme is defined