487e81f45573d912b01936346ba20a408d10860f
[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);
2794     unset($SESSION->encoding);
2795     $SESSION->encoding = current_charset();
2797     // Restore the calendar filters, if saved
2798     if (intval(get_user_preferences('calendar_persistflt', 0))) {
2799         include_once($CFG->dirroot.'/calendar/lib.php');
2800         calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
2801     }
2805 /**
2806  * Enrols (or re-enrols) a student in a given course
2807  *
2808  * NOTE: Defaults to 'manual' enrolment - enrolment plugins
2809  * must set it explicitly.
2810  *
2811  * @uses $CFG
2812  * @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.
2813  * @param int $courseid The id of the course that is being viewed
2814  * @param int $timestart ?
2815  * @param int $timeend ?
2816  * @param string $enrol ?
2817  * @return bool
2818  * @todo Finish documenting this function
2819  */
2820 function enrol_student($userid, $courseid, $timestart=0, $timeend=0, $enrol='manual') {
2822     global $CFG, $USER;
2824     if (!$course = get_record('course', 'id', $courseid)) {  // Check course
2825         return false;
2826     }
2827     if (!$user = get_record('user', 'id', $userid)) {        // Check user
2828         return false;
2829     }
2830     // enrol the student in any parent meta courses...
2831     if ($parents = get_records('course_meta', 'child_course', $courseid)) {
2832         foreach ($parents as $parent) {
2833             enrol_student($userid, $parent->parent_course, $timestart, $timeend,'metacourse');
2834             // if we're enrolling ourselves in the child course, add the parent courses to USER too
2835             // otherwise they'll have to logout and in again to get it
2836             // http://moodle.org/mod/forum/post.php?reply=185699
2837             if (!empty($USER) && $userid == $USER->id) {
2838                 $USER->student[$parent->parent_course] = true;
2839             }
2840         }
2841     }
2843     if ($student = get_record('user_students', 'userid', $userid, 'course', $courseid)) {
2844         $student->timestart = $timestart;
2845         $student->timeend = $timeend;
2846         $student->time = time();
2847         $student->enrol = $enrol;
2848         return update_record('user_students', $student);
2850     } else {
2851         require_once("$CFG->dirroot/mod/forum/lib.php");
2852         forum_add_user($userid, $courseid);
2854         $student->userid = $userid;
2855         $student->course = $courseid;
2856         $student->timestart = $timestart;
2857         $student->timeend = $timeend;
2858         $student->time = time();
2859         $student->enrol = $enrol;
2860         return insert_record('user_students', $student);
2861     }
2864 /**
2865  * Unenrols a student from a given course
2866  *
2867  * @param int $courseid The id of the course that is being viewed, if any
2868  * @param int $userid The id of the user that is being tested against.
2869  * @return bool
2870  */
2871 function unenrol_student($userid, $courseid=0) {
2872     global $CFG;
2874     if ($courseid) {
2875         /// First delete any crucial stuff that might still send mail
2876         if ($forums = get_records('forum', 'course', $courseid)) {
2877             foreach ($forums as $forum) {
2878                 delete_records('forum_subscriptions', 'forum', $forum->id, 'userid', $userid);
2879             }
2880         }
2881         if ($groups = get_groups($courseid, $userid)) {
2882             foreach ($groups as $group) {
2883                 delete_records('groups_members', 'groupid', $group->id, 'userid', $userid);
2884             }
2885         }
2886         // unenrol the student from any parent meta courses...
2887         if ($parents = get_records('course_meta','child_course',$courseid)) {
2888             foreach ($parents as $parent) {
2889                 if (!record_exists_sql('SELECT us.id FROM '.$CFG->prefix.'user_students us, '
2890                                        .$CFG->prefix.'course_meta cm WHERE cm.child_course = us.course
2891                                         AND us.userid = '.$userid .' AND us.course != '.$courseid)) {
2892                     unenrol_student($userid, $parent->parent_course);
2893                 }
2894             }
2895         }
2896         return delete_records('user_students', 'userid', $userid, 'course', $courseid);
2898     } else {
2899         delete_records('forum_subscriptions', 'userid', $userid);
2900         delete_records('groups_members', 'userid', $userid);
2901         return delete_records('user_students', 'userid', $userid);
2902     }
2905 /**
2906  * Add a teacher to a given course
2907  *
2908  * @uses $USER
2909  * @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.
2910  * @param int $courseid The id of the course that is being viewed, if any
2911  * @param int $editall ?
2912  * @param string $role ?
2913  * @param int $timestart ?
2914  * @param int $timeend ?
2915  * @param string $enrol ?
2916  * @return bool
2917  * @todo Finish documenting this function
2918  */
2919 function add_teacher($userid, $courseid, $editall=1, $role='', $timestart=0, $timeend=0, $enrol='manual') {
2920     global $CFG;
2922     if ($teacher = get_record('user_teachers', 'userid', $userid, 'course', $courseid)) {
2923         $newteacher = NULL;
2924         $newteacher->id = $teacher->id;
2925         $newteacher->editall = $editall;
2926         $newteacher->enrol = $enrol;
2927         if ($role) {
2928             $newteacher->role = $role;
2929         }
2930         if ($timestart) {
2931             $newteacher->timestart = $timestart;
2932         }
2933         if ($timeend) {
2934             $newteacher->timeend = $timeend;
2935         }
2936         return update_record('user_teachers', $newteacher);
2937     }
2939     if (!record_exists('user', 'id', $userid)) {
2940         return false;   // no such user
2941     }
2943     if (!record_exists('course', 'id', $courseid)) {
2944         return false;   // no such course
2945     }
2947     $teacher = NULL;
2948     $teacher->userid  = $userid;
2949     $teacher->course  = $courseid;
2950     $teacher->editall = $editall;
2951     $teacher->role    = $role;
2952     $teacher->enrol   = $enrol;
2953     $teacher->timemodified = time();
2954     $teacher->timestart = $timestart;
2955     $teacher->timeend = $timeend;
2956     if ($student = get_record('user_students', 'userid', $userid, 'course', $courseid)) {
2957         $teacher->timestart = $student->timestart;
2958         $teacher->timeend = $student->timeend;
2959         $teacher->timeaccess = $student->timeaccess;
2960     }
2962     if (record_exists('user_teachers', 'course', $courseid)) {
2963         $teacher->authority = 2;
2964     } else {
2965         $teacher->authority = 1;
2966     }
2967     delete_records('user_students', 'userid', $userid, 'course', $courseid); // Unenrol as student
2969     /// Add forum subscriptions for new users
2970     require_once($CFG->dirroot.'/mod/forum/lib.php');
2971     forum_add_user($userid, $courseid);
2973     return insert_record('user_teachers', $teacher);
2977 /**
2978  * Removes a teacher from a given course (or ALL courses)
2979  * Does not delete the user account
2980  *
2981  * @param int $courseid The id of the course that is being viewed, if any
2982  * @param int $userid The id of the user that is being tested against.
2983  * @return bool
2984  */
2985 function remove_teacher($userid, $courseid=0) {
2986     if ($courseid) {
2987         /// First delete any crucial stuff that might still send mail
2988         if ($forums = get_records('forum', 'course', $courseid)) {
2989             foreach ($forums as $forum) {
2990                 delete_records('forum_subscriptions', 'forum', $forum->id, 'userid', $userid);
2991             }
2992         }
2994         /// Next if the teacher is not registered as a student, but is
2995         /// a member of a group, remove them from the group.
2996         if (!isstudent($courseid, $userid)) {
2997             if ($groups = get_groups($courseid, $userid)) {
2998                 foreach ($groups as $group) {
2999                     delete_records('groups_members', 'groupid', $group->id, 'userid', $userid);
3000                 }
3001             }
3002         }
3004         return delete_records('user_teachers', 'userid', $userid, 'course', $courseid);
3005     } else {
3006         delete_records('forum_subscriptions', 'userid', $userid);
3007         return delete_records('user_teachers', 'userid', $userid);
3008     }
3011 /**
3012  * Add a creator to the site
3013  *
3014  * @param int $userid The id of the user that is being tested against.
3015  * @return bool
3016  */
3017 function add_creator($userid) {
3019     if (!record_exists('user_admins', 'userid', $userid)) {
3020         if (record_exists('user', 'id', $userid)) {
3021             $creator->userid = $userid;
3022             return insert_record('user_coursecreators', $creator);
3023         }
3024         return false;
3025     }
3026     return true;
3029 /**
3030  * Remove a creator from a site
3031  *
3032  * @uses $db
3033  * @param int $userid The id of the user that is being tested against.
3034  * @return bool
3035  */
3036 function remove_creator($userid) {
3037     global $db;
3039     return delete_records('user_coursecreators', 'userid', $userid);
3042 /**
3043  * Add an admin to a site
3044  *
3045  * @uses SITEID
3046  * @param int $userid The id of the user that is being tested against.
3047  * @return bool
3048  */
3049 function add_admin($userid) {
3051     if (!record_exists('user_admins', 'userid', $userid)) {
3052         if (record_exists('user', 'id', $userid)) {
3053             $admin->userid = $userid;
3055             // any admin is also a teacher on the site course
3056             if (!record_exists('user_teachers', 'course', SITEID, 'userid', $userid)) {
3057                 if (!add_teacher($userid, SITEID)) {
3058                     return false;
3059                 }
3060             }
3062             return insert_record('user_admins', $admin);
3063         }
3064         return false;
3065     }
3066     return true;
3069 /**
3070  * Removes an admin from a site
3071  *
3072  * @uses $db
3073  * @uses SITEID
3074  * @param int $userid The id of the user that is being tested against.
3075  * @return bool
3076  */
3077 function remove_admin($userid) {
3078     global $db;
3080     // remove also from the list of site teachers
3081     remove_teacher($userid, SITEID);
3083     return delete_records('user_admins', 'userid', $userid);
3086 /**
3087  * Delete a course, including all related data from the database,
3088  * and any associated files from the moodledata folder.
3089  *
3090  * @param int $courseid The id of the course to delete.
3091  * @param bool $showfeedback Whether to display notifications of each action the function performs.
3092  * @return bool true if all the removals succeeded. false if there were any failures. If this
3093  *             method returns false, some of the removals will probably have succeeded, and others
3094  *             failed, but you have no way of knowing which.
3095  */
3096 function delete_course($courseid, $showfeedback = true) {
3097     global $CFG;
3098     $result = true;
3100     if (!remove_course_contents($courseid, $showfeedback)) {
3101         if ($showfeedback) {
3102             notify("An error occurred while deleting some of the course contents.");
3103         }
3104         $result = false;
3105     }
3107     if (!delete_records("course", "id", $courseid)) {
3108         if ($showfeedback) {
3109             notify("An error occurred while deleting the main course record.");
3110         }
3111         $result = false;
3112     }
3114     if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3115         if ($showfeedback) {
3116             notify("An error occurred while deleting the course files.");
3117         }
3118         $result = false;
3119     }
3121     return $result;
3124 /**
3125  * Clear a course out completely, deleting all content
3126  * but don't delete the course itself
3127  *
3128  * @uses $CFG
3129  * @param int $courseid The id of the course that is being deleted
3130  * @param bool $showfeedback Whether to display notifications of each action the function performs.
3131  * @return bool true if all the removals succeeded. false if there were any failures. If this
3132  *             method returns false, some of the removals will probably have succeeded, and others
3133  *             failed, but you have no way of knowing which.
3134  */
3135 function remove_course_contents($courseid, $showfeedback=true) {
3137     global $CFG;
3139     $result = true;
3141     if (! $course = get_record('course', 'id', $courseid)) {
3142         error('Course ID was incorrect (can\'t find it)');
3143     }
3145     $strdeleted = get_string('deleted');
3147     // First delete every instance of every module
3149     if ($allmods = get_records('modules') ) {
3150         foreach ($allmods as $mod) {
3151             $modname = $mod->name;
3152             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3153             $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance
3154             $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon)
3155             $count=0;
3156             if (file_exists($modfile)) {
3157                 include_once($modfile);
3158                 if (function_exists($moddelete)) {
3159                     if ($instances = get_records($modname, 'course', $course->id)) {
3160                         foreach ($instances as $instance) {
3161                             if ($moddelete($instance->id)) {
3162                                 $count++;
3163                             } else {
3164                                 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
3165                                 $result = false;
3166                             }
3167                         }
3168                     }
3169                 } else {
3170                     notify('Function '. $moddelete() .'doesn\'t exist!');
3171                     $result = false;
3172                 }
3174                 if (function_exists($moddeletecourse)) {
3175                     $moddeletecourse($course);
3176                 }
3177             }
3178             if ($showfeedback) {
3179                 notify($strdeleted .' '. $count .' x '. $modname);
3180             }
3181         }
3182     } else {
3183         error('No modules are installed!');
3184     }
3186     // Give local code a chance to delete its references to this course.
3187     require_once('locallib.php');
3188     notify_local_delete_course($courseid, $showfeedback);
3190     // Delete course blocks
3191     if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3192         if ($showfeedback) {
3193             notify($strdeleted .' block_instance');
3194         }
3195     } else {
3196         $result = false;
3197     }
3199     // Delete Other stuff.
3200     // This array stores the tables that need to be cleared, as
3201     // table_name => column_name that contains the course id.
3202     $tablestoclear = array(
3203         'user_students' => 'course', // Delete any user stuff
3204         'user_teachers' => 'course',
3205         'event' => 'courseid', // Delete events
3206         'log' => 'course', // Delete logs
3207         'course_sections' => 'course', // Delete any course stuff
3208         'course_modules' => 'course',
3209         'grade_category' => 'courseid', // Delete gradebook stuff
3210         'grade_exceptions' => 'courseid',
3211         'grade_item' => 'courseid',
3212         'grade_letter' => 'courseid',
3213         'grade_preferences' => 'courseid'
3214     );
3215     foreach ($tablestoclear as $table => $col) {
3216         if (delete_records($table, $col, $course->id)) {
3217             if ($showfeedback) {
3218                 notify($strdeleted . ' ' . $table);
3219             }
3220         } else {
3221             $result = false;
3222         }
3223     }
3225     // Delete any groups
3226     if ($groups = get_records('groups', 'courseid', $course->id)) {
3227         foreach ($groups as $group) {
3228             if (delete_records('groups_members', 'groupid', $group->id)) {
3229                 if ($showfeedback) {
3230                     notify($strdeleted .' groups_members');
3231                 }
3232             } else {
3233                 $result = false;
3234             }
3235             if (delete_records('groups', 'id', $group->id)) {
3236                 if ($showfeedback) {
3237                     notify($strdeleted .' groups');
3238                 }
3239             } else {
3240                 $result = false;
3241             }
3242         }
3243     }
3245     if ($course->metacourse) {
3246         delete_records("course_meta","parent_course",$course->id);
3247         sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3248         if ($showfeedback) {
3249             notify("$strdeleted course_meta");
3250         }
3251     } else {
3252         if ($parents = get_records("course_meta","child_course",$course->id)) {
3253             foreach ($parents as $parent) {
3254                 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3255             }
3256             if ($showfeedback) {
3257                 notify("$strdeleted course_meta");
3258             }
3259         }
3260     }
3262     return $result;
3266 /**
3267  * This function will empty a course of user data as much as
3268  * possible. It will retain the activities and the structure
3269  * of the course.
3270  *
3271  * @uses $CFG
3272  * @param int $courseid The id of the course that is being emptied of user data.
3273  * @param bool $showfeedback Whether to display notifications of each action the function performs.
3274  * @param bool $removestudents Whether to remove matching records from the user_students and groups_members table.
3275  * @param bool $removeteachers Whether to remove matching records from the user_teachers table.
3276  * @param bool $removegroups Whether to remove matching records from the groups table.
3277  * @param bool $removeevents Whether to remove matching records from the event table.
3278  * @param bool $removelogs Whether to remove matching records from the log table.
3279  * @return bool true if all the removals succeeded. false if there were any failures. If this
3280  *             method returns false, some of the removals will probably have succeeded, and others
3281  *             failed, but you have no way of knowing which.
3282  */
3283 function remove_course_userdata($courseid, $showfeedback=true,
3284                                 $removestudents=true, $removeteachers=false, $removegroups=true,
3285                                 $removeevents=true, $removelogs=false) {
3287     global $CFG;
3289     $result = true;
3291     if (! $course = get_record('course', 'id', $courseid)) {
3292         error('Course ID was incorrect (can\'t find it)');
3293     }
3295     $strdeleted = get_string('deleted');
3297     // Look in every instance of every module for data to delete
3299     if ($allmods = get_records('modules') ) {
3300         foreach ($allmods as $mod) {
3301             $modname = $mod->name;
3302             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3303             $moddeleteuserdata = $modname .'_delete_userdata';   // Function to delete user data
3304             $count=0;
3305             if (file_exists($modfile)) {
3306                 @include_once($modfile);
3307                 if (function_exists($moddeleteuserdata)) {
3308                     $moddeleteuserdata($course, $showfeedback);
3309                 }
3310             }
3311         }
3312     } else {
3313         error('No modules are installed!');
3314     }
3316     // Delete other stuff
3318     if ($removestudents) {
3319         /// Delete student enrolments
3320         if (delete_records('user_students', 'course', $course->id)) {
3321             if ($showfeedback) {
3322                 notify($strdeleted .' user_students');
3323             }
3324         } else {
3325             $result = false;
3326         }
3327         /// Delete group members (but keep the groups)
3328         if ($groups = get_records('groups', 'courseid', $course->id)) {
3329             foreach ($groups as $group) {
3330                 if (delete_records('groups_members', 'groupid', $group->id)) {
3331                     if ($showfeedback) {
3332                         notify($strdeleted .' groups_members');
3333                     }
3334                 } else {
3335                     $result = false;
3336                 }
3337             }
3338         }
3339     }
3341     if ($removeteachers) {
3342         if (delete_records('user_teachers', 'course', $course->id)) {
3343             if ($showfeedback) {
3344                 notify($strdeleted .' user_teachers');
3345             }
3346         } else {
3347             $result = false;
3348         }
3349     }
3351     if ($removegroups) {
3352         if ($groups = get_records('groups', 'courseid', $course->id)) {
3353             foreach ($groups as $group) {
3354                 if (delete_records('groups', 'id', $group->id)) {
3355                     if ($showfeedback) {
3356                         notify($strdeleted .' groups');
3357                     }
3358                 } else {
3359                     $result = false;
3360                 }
3361             }
3362         }
3363     }
3365     if ($removeevents) {
3366         if (delete_records('event', 'courseid', $course->id)) {
3367             if ($showfeedback) {
3368                 notify($strdeleted .' event');
3369             }
3370         } else {
3371             $result = false;
3372         }
3373     }
3375     if ($removelogs) {
3376         if (delete_records('log', 'course', $course->id)) {
3377             if ($showfeedback) {
3378                 notify($strdeleted .' log');
3379             }
3380         } else {
3381             $result = false;
3382         }
3383     }
3385     return $result;
3389 /// GROUPS /////////////////////////////////////////////////////////
3392 /**
3393  * Determines if the user a member of the given group
3394  *
3395  * @uses $USER
3396  * @param int $groupid The group to check the membership of
3397  * @param int $userid The user to check against the group
3398  * @return bool
3399  */
3400 function ismember($groupid, $userid=0) {
3401     global $USER;
3403     if (!$groupid) {   // No point doing further checks
3404         return false;
3405     }
3406     //if groupid is supplied in array format
3407     if (!$userid) {
3408         if (empty($USER->groupmember)) {
3409             return false;
3410         }
3411         //changed too for multiple groups
3412         foreach ($USER->groupmember as $courseid => $mgroupid) {
3413             //need to loop one more time...
3414             if (is_array($mgroupid)) {
3415                 foreach ($mgroupid as $index => $mygroupid) {
3416                     if ($mygroupid == $groupid) {
3417                         return true;
3418                     }
3419                 }
3420             } else if ($mygroupid == $groupid) {
3421                 return true;
3422             }
3423         }
3424         return false;
3425     }
3427     if (is_array($groupid)){
3428         foreach ($groupid as $index => $val){
3429             if (record_exists('groups_members', 'groupid', $val, 'userid', $userid)){
3430                 return true;
3431             }
3432         }
3433     }
3434     else {
3435         return record_exists('groups_members', 'groupid', $groupid, 'userid', $userid);
3436     }
3437     return false;
3439     //else group id is in single format
3441     //return record_exists('groups_members', 'groupid', $groupid, 'userid', $userid);
3444 /**
3445  * Add a user to a group, return true upon success or if user already a group member
3446  *
3447  * @param int $groupid  The group id to add user to
3448  * @param int $userid   The user id to add to the group
3449  * @return bool
3450  */
3451 function add_user_to_group ($groupid, $userid) {
3452     if (ismember($groupid, $userid)) return true;
3453     $record->groupid = $groupid;
3454     $record->userid = $userid;
3455     $record->timeadded = time();
3456     return (insert_record('groups_members', $record) !== false);
3460 /**
3461  * Get the group ID of the current user in the given course
3462  *
3463  * @uses $USER
3464  * @param int $courseid The course being examined - relates to id field in 'course' table.
3465  * @return int
3466  */
3467 function mygroupid($courseid) {
3468     global $USER;
3469     if (empty($USER->groupmember[$courseid])) {
3470         return 0;
3471     } else {
3472         //this is an array of ids >.<
3473         return $USER->groupmember[$courseid];
3474     }
3477 /**
3478  * For a given course, and possibly course module, determine
3479  * what the current default groupmode is:
3480  * NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3481  *
3482  * @param course $course A {@link $COURSE} object
3483  * @param object $cm A course module object
3484  * @return int A group mode (NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS)
3485  */
3486 function groupmode($course, $cm=null) {
3488     if ($cm and !$course->groupmodeforce) {
3489         return $cm->groupmode;
3490     }
3491     return $course->groupmode;
3495 /**
3496  * Sets the current group in the session variable
3497  *
3498  * @uses $SESSION
3499  * @param int $courseid The course being examined - relates to id field in 'course' table.
3500  * @param int $groupid The group being examined.
3501  * @return int Current group id which was set by this function
3502  */
3503 function set_current_group($courseid, $groupid) {
3504     global $SESSION;
3506     return $SESSION->currentgroup[$courseid] = $groupid;
3510 /**
3511  * Gets the current group for the current user as an id or an object
3512  *
3513  * @uses $USER
3514  * @uses $SESSION
3515  * @param int $courseid The course being examined - relates to id field in 'course' table.
3516  * @param bool $full If true, the return value is a full record object. If false, just the id of the record.
3517  */
3518 function get_current_group($courseid, $full=false) {
3519     global $SESSION, $USER;
3521     if (!isset($SESSION->currentgroup[$courseid])) {
3522         if (empty($USER->groupmember[$courseid]) or isteacheredit($courseid)) {
3524             return 0;
3525         } else {
3526             //trying to add a hack >.<, always first select the first one in list
3527             $SESSION->currentgroup[$courseid] = $USER->groupmember[$courseid][0];
3528         }
3529     }
3531     if ($full) {
3532         return get_record('groups', 'id', $SESSION->currentgroup[$courseid]);
3533     } else {
3534         return $SESSION->currentgroup[$courseid];
3535     }
3538 /**
3539  * A combination function to make it easier for modules
3540  * to set up groups.
3541  *
3542  * It will use a given "groupid" parameter and try to use
3543  * that to reset the current group for the user.
3544  *
3545  * @uses VISIBLEGROUPS
3546  * @param course $course A {@link $COURSE} object
3547  * @param int $groupmode Either NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3548  * @param int $groupid Will try to use this optional parameter to
3549  *            reset the current group for the user
3550  * @return int|false Returns the current group id or false if error.
3551  */
3552 function get_and_set_current_group($course, $groupmode, $groupid=-1) {
3554     if (!$groupmode) {   // Groups don't even apply
3555         return false;
3556     }
3558     $currentgroupid = get_current_group($course->id);
3560     if ($groupid < 0) {  // No change was specified
3561         return $currentgroupid;
3562     }
3564     if ($groupid) {      // Try to change the current group to this groupid
3565         if ($group = get_record('groups', 'id', $groupid, 'courseid', $course->id)) { // Exists
3566             if (isteacheredit($course->id)) {          // Sets current default group
3567                 $currentgroupid = set_current_group($course->id, $group->id);
3569             } else if ($groupmode == VISIBLEGROUPS) {
3570                   // All groups are visible
3571                 //if (ismember($group->id)){
3572                     $currentgroupid = set_current_group($course->id, $group->id);//set this since he might post
3573                 /*)}else {
3574                     $currentgroupid = $group->id;*/
3575             } else if ($groupmode == SEPARATEGROUPS) { // student in separate groups switching
3576                 if (ismember($group->id)){//check if is a member
3577                     $currentgroupid = set_current_group($course->id, $group->id); //might need to set_current_group?
3578                 }
3579                 else {
3580                     echo ($group->id);
3581                     notify('you do not belong to this group!',error);
3582                 }
3583             }
3584         }
3585     } else {             // When groupid = 0 it means show ALL groups
3586         //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)
3587         if (isteacheredit($course->id) OR (isteacher($course->id) AND ($groupmode == VISIBLEGROUPS))) {          // Sets current default group
3588             $currentgroupid = set_current_group($course->id, 0);
3590         } else if ($groupmode == VISIBLEGROUPS) {  // All groups are visible
3591             $currentgroupid = 0;
3592         }
3593     }
3595     return $currentgroupid;
3599 /**
3600  * A big combination function to make it easier for modules
3601  * to set up groups.
3602  *
3603  * Terminates if the current user shouldn't be looking at this group
3604  * Otherwise returns the current group if there is one
3605  * Otherwise returns false if groups aren't relevant
3606  *
3607  * @uses SEPARATEGROUPS
3608  * @uses VISIBLEGROUPS
3609  * @param course $course A {@link $COURSE} object
3610  * @param int $groupmode Either NOGROUPS, SEPARATEGROUPS or VISIBLEGROUPS
3611  * @param string $urlroot ?
3612  * @return int|false
3613  */
3614 function setup_and_print_groups($course, $groupmode, $urlroot) {
3616     global $USER, $SESSION; //needs his id, need to hack his groups in session
3618     if (isset($_GET['group'])) {
3619         $changegroup = $_GET['group'];  /// 0 or higher
3620     } else {
3621         $changegroup = -1;              /// This means no group change was specified
3622     }
3624     $currentgroup = get_and_set_current_group($course, $groupmode, $changegroup);
3625     if ($currentgroup === false) {
3626         return false;
3627     }
3629     if ($groupmode == SEPARATEGROUPS and !isteacheredit($course->id) and !$currentgroup) {
3630         //we are in separate groups and the current group is group 0, as last set.
3631         //this can mean that either, this guy has no group
3632         //or, this guy just came from a visible all forum, and he left when he set his current group to 0 (show all)
3634         //for the second situation, we need to perform the trick and get him a group.
3635         $courseid = $course->id;
3636         if (!empty($USER->groupmember[$courseid])){
3637             $currentgroup = get_and_set_current_group($course, $groupmode, $USER->groupmember[$courseid][0]);
3638         }
3639         else {//else he has no group in this course
3640             print_heading(get_string('notingroup'));
3641             print_footer($course);
3642             exit;
3643         }
3644     }
3646     if ($groupmode == VISIBLEGROUPS or ($groupmode and isteacheredit($course->id))) {
3647         if ($groups = get_records_menu('groups', 'courseid', $course->id, 'name ASC', 'id,name')) {
3648             echo '<div align="center">';
3649             print_group_menu($groups, $groupmode, $currentgroup, $urlroot);
3650             echo '</div>';
3651         }
3652     }//added code here to allow non-editting teacher to swap in-between his own groups
3653     //added code for students in separategrous to swtich groups
3654     else if ($groupmode == SEPARATEGROUPS and (isteacher($course->id) or isstudent($course->id))) {
3655         $validgroups = array();
3656         //get all the groups this guy is in in this course
3657         if ($p = user_group($course->id,$USER->id)){
3658             //extract the name and id for the group
3659             foreach ($p as $index => $object){
3660                 $validgroups[$object->id] = $object->name;
3661             }
3662             echo '<div align="center">';
3663             //print them in the menu
3664             print_group_menu($validgroups, $groupmode, $currentgroup, $urlroot,0);
3665             echo '</div>';
3666         }
3667     }
3669     return $currentgroup;
3672 function generate_email_processing_address($modid,$modargs) {
3673     global $CFG;
3675     if (empty($CFG->siteidentifier)) {    // Unique site identification code
3676         set_config('siteidentifier', random_string(32));
3677     }
3679     $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3680     return $header . substr(md5($header.$CFG->siteidentifier),0,16).'@'.$CFG->maildomain;
3684 function moodle_process_email($modargs,$body) {
3685     // the first char should be an unencoded letter. We'll take this as an action
3686     switch ($modargs{0}) {
3687         case 'B': { // bounce
3688             list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3689             if ($user = get_record_select("user","id=$userid","id,email")) {
3690                 // check the half md5 of their email
3691                 $md5check = substr(md5($user->email),0,16);
3692                 if ($md5check == substr($modargs, -16)) {
3693                     set_bounce_count($user);
3694                 }
3695                 // else maybe they've already changed it?
3696             }
3697         }
3698         break;
3699         // maybe more later?
3700     }
3703 /// CORRESPONDENCE  ////////////////////////////////////////////////
3705 /**
3706  * Send an email to a specified user
3707  *
3708  * @uses $CFG
3709  * @uses $FULLME
3710  * @uses SITEID
3711  * @param user $user  A {@link $USER} object
3712  * @param user $from A {@link $USER} object
3713  * @param string $subject plain text subject line of the email
3714  * @param string $messagetext plain text version of the message
3715  * @param string $messagehtml complete html version of the message (optional)
3716  * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
3717  * @param string $attachname the name of the file (extension indicates MIME)
3718  * @param bool $usetrueaddress determines whether $from email address should
3719  *          be sent out. Will be overruled by user profile setting for maildisplay
3720  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3721  *          was blocked by user and "false" if there was another sort of error.
3722  */
3723 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $repyto='', $replytoname='') {
3725     global $CFG, $FULLME;
3727     global $course;                // This is a bit of an ugly hack to be gotten rid of later
3728     if (!empty($course->lang)) {   // Course language is defined
3729         $CFG->courselang = $course->lang;
3730     }
3731     if (!empty($course->theme)) {   // Course theme is defined
3732         $CFG->coursetheme = $course->theme;
3733     }
3735     include_once($CFG->libdir .'/phpmailer/class.phpmailer.php');
3737     if (empty($user)) {
3738         return false;
3739     }
3741     if (!empty($user->emailstop)) {
3742         return 'emailstop';
3743     }
3745     if (over_bounce_threshold($user)) {
3746         error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
3747         return false;
3748     }
3750     $mail = new phpmailer;
3752     $mail->Version = 'Moodle '. $CFG->version;           // mailer version
3753     $mail->PluginDir = $CFG->libdir .'/phpmailer/';      // plugin directory (eg smtp plugin)
3755     $mail->CharSet = current_charset(true);              //User charset, recalculating it in each call
3757     if ($CFG->smtphosts == 'qmail') {
3758         $mail->IsQmail();                              // use Qmail system
3760     } else if (empty($CFG->smtphosts)) {
3761         $mail->IsMail();                               // use PHP mail() = sendmail
3763     } else {
3764         $mail->IsSMTP();                               // use SMTP directly
3765         if ($CFG->debug > 7) {
3766             echo '<pre>' . "\n";
3767             $mail->SMTPDebug = true;
3768         }
3769         $mail->Host = $CFG->smtphosts;               // specify main and backup servers
3771         if ($CFG->smtpuser) {                          // Use SMTP authentication
3772             $mail->SMTPAuth = true;
3773             $mail->Username = $CFG->smtpuser;
3774             $mail->Password = $CFG->smtppass;
3775         }
3776     }
3778     $adminuser = get_admin();
3780     // make up an email address for handling bounces
3781     if (!empty($CFG->handlebounces)) {
3782         $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
3783         $mail->Sender = generate_email_processing_address(0,$modargs);
3784     }
3785     else {
3786         $mail->Sender   = $adminuser->email;
3787     }
3789     if (is_string($from)) { // So we can pass whatever we want if there is need
3790         $mail->From     = $CFG->noreplyaddress;
3791         $mail->FromName = $from;
3792     } else if ($usetrueaddress and $from->maildisplay) {
3793         $mail->From     = $from->email;
3794         $mail->FromName = fullname($from);
3795     } else {
3796         $mail->From     = $CFG->noreplyaddress;
3797         $mail->FromName = fullname($from);
3798         if (empty($replyto)) {
3799             $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
3800         }
3801     }
3803     if (!empty($replyto)) {
3804         $mail->AddReplyTo($replyto,$replytoname);
3805     }
3807     $mail->Subject = substr(stripslashes($subject), 0, 900);
3809     $mail->AddAddress($user->email, fullname($user) );
3811     $mail->WordWrap = 79;                               // set word wrap
3813     if (!empty($from->customheaders)) {                 // Add custom headers
3814         if (is_array($from->customheaders)) {
3815             foreach ($from->customheaders as $customheader) {
3816                 $mail->AddCustomHeader($customheader);
3817             }
3818         } else {
3819             $mail->AddCustomHeader($from->customheaders);
3820         }
3821     }
3823     if (!empty($from->priority)) {
3824         $mail->Priority = $from->priority;
3825     }
3827     if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
3828         $mail->IsHTML(true);
3829         $mail->Encoding = 'quoted-printable';           // Encoding to use
3830         $mail->Body    =  $messagehtml;
3831         $mail->AltBody =  "\n$messagetext\n";
3832     } else {
3833         $mail->IsHTML(false);
3834         $mail->Body =  "\n$messagetext\n";
3835     }
3837     if ($attachment && $attachname) {
3838         if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
3839             $mail->AddAddress($adminuser->email, fullname($adminuser) );
3840             $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
3841         } else {
3842             require_once($CFG->libdir.'/filelib.php');
3843             $mimetype = mimeinfo('type', $attachname);
3844             $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
3845         }
3846     }
3848     if ($mail->Send()) {
3849         set_send_count($user);
3850         return true;
3851     } else {
3852         mtrace('ERROR: '. $mail->ErrorInfo);
3853         add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
3854         return false;
3855     }
3858 /**
3859  * Sets specified user's password and send the new password to the user via email.
3860  *
3861  * @uses $CFG
3862  * @param user $user A {@link $USER} object
3863  * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
3864  *          was blocked by user and "false" if there was another sort of error.
3865  */
3866 function setnew_password_and_mail($user) {
3868     global $CFG;
3870     $site  = get_site();
3871     $from = get_admin();
3873     $newpassword = generate_password();
3875     if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
3876         trigger_error('Could not set user password!');
3877         return false;
3878     }
3880     $a->firstname   = $user->firstname;
3881     $a->sitename    = $site->fullname;
3882     $a->username    = $user->username;
3883     $a->newpassword = $newpassword;
3884     $a->link        = $CFG->wwwroot .'/login/';
3885     $a->signoff     = fullname($from, true).' ('. $from->email .')';
3887     $message = get_string('newusernewpasswordtext', '', $a);
3889     $subject  = $site->fullname .': '. get_string('newusernewpasswordsubj');
3891     return email_to_user($user, $from, $subject, $message);
3895 /**
3896  * Resets specified user's password and send the new password to the user via email.
3897  *
3898  * @uses $CFG
3899  * @param user $user A {@link $USER} object
3900  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3901  *          was blocked by user and "false" if there was another sort of error.
3902  */
3903 function reset_password_and_mail($user) {
3905     global $CFG;
3907     $site  = get_site();
3908     $from = get_admin();
3910     $external = false;
3911     if (!is_internal_auth($user->auth)) {
3912         include_once($CFG->dirroot . '/auth/' . $user->auth . '/lib.php');
3913         if (empty($CFG->{'auth_'.$user->auth.'_stdchangepassword'})
3914             || !function_exists('auth_user_update_password')) {
3915             trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
3916             return false;
3917         } else {
3918             $external = true;
3919         }
3920     }
3922     $newpassword = generate_password();
3924     if ($external) {
3925         if (!auth_user_update_password($user->username, $newpassword)) {
3926             error("Could not set user password!");
3927         }
3928     } else {
3929         if (! set_field("user", "password", md5($newpassword), "id", $user->id) ) {
3930             error("Could not set user password!");
3931         }
3932     }
3934     $a->firstname = $user->firstname;
3935     $a->sitename = $site->fullname;
3936     $a->username = $user->username;
3937     $a->newpassword = $newpassword;
3938     $a->link = $CFG->httpswwwroot .'/login/change_password.php';
3939     $a->signoff = fullname($from, true).' ('. $from->email .')';
3941     $message = get_string('newpasswordtext', '', $a);
3943     $subject  = $site->fullname .': '. get_string('changedpassword');
3945     return email_to_user($user, $from, $subject, $message);
3949 /**
3950  * Send email to specified user with confirmation text and activation link.
3951  *
3952  * @uses $CFG
3953  * @param user $user A {@link $USER} object
3954  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3955  *          was blocked by user and "false" if there was another sort of error.
3956  */
3957  function send_confirmation_email($user) {
3959     global $CFG;
3961     $site = get_site();
3962     $from = get_admin();
3964     $data->firstname = fullname($user);
3965     $data->sitename = $site->fullname;
3966     $data->admin = fullname($from) .' ('. $from->email .')';
3968     $subject = get_string('emailconfirmationsubject', '', $site->fullname);
3970     /// Make the text version a normal link for normal people
3971     $data->link = $CFG->wwwroot .'/login/confirm.php?p='. $user->secret .'&s='. $user->username;
3972     $message = get_string('emailconfirmation', '', $data);
3974     /// Make the HTML version more XHTML happy  (&amp;)
3975     $data->link = $CFG->wwwroot .'/login/confirm.php?p='. $user->secret .'&amp;s='. $user->username;
3976     $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
3978     $user->mailformat = 1;  // Always send HTML version as well
3980     return email_to_user($user, $from, $subject, $message, $messagehtml);
3984 /**
3985  * send_password_change_confirmation_email.
3986  *
3987  * @uses $CFG
3988  * @param user $user A {@link $USER} object
3989  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3990  *          was blocked by user and "false" if there was another sort of error.
3991  */
3992 function send_password_change_confirmation_email($user) {
3994     global $CFG;
3996     $site = get_site();
3997     $from = get_admin();
3999     $data->firstname = $user->firstname;
4000     $data->sitename = $site->fullname;
4001     $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. $user->username;
4002     $data->admin = fullname($from).' ('. $from->email .')';
4004     $message = get_string('emailpasswordconfirmation', '', $data);
4005     $subject = get_string('emailpasswordconfirmationsubject', '', $site->fullname);
4007     return email_to_user($user, $from, $subject, $message);
4011 /**
4012  * Check that an email is allowed.  It returns an error message if there
4013  * was a problem.
4014  *
4015  * @uses $CFG
4016  * @param  string $email Content of email
4017  * @return string|false
4018  */
4019 function email_is_not_allowed($email) {
4021     global $CFG;
4023     if (!empty($CFG->allowemailaddresses)) {
4024         $allowed = explode(' ', $CFG->allowemailaddresses);
4025         foreach ($allowed as $allowedpattern) {
4026             $allowedpattern = trim($allowedpattern);
4027             if (!$allowedpattern) {
4028                 continue;
4029             }
4030             if (strpos($email, $allowedpattern) !== false) {  // Match!
4031                 return false;
4032             }
4033         }
4034         return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
4036     } else if (!empty($CFG->denyemailaddresses)) {
4037         $denied = explode(' ', $CFG->denyemailaddresses);
4038         foreach ($denied as $deniedpattern) {
4039             $deniedpattern = trim($deniedpattern);
4040             if (!$deniedpattern) {
4041                 continue;
4042             }
4043             if (strpos($email, $deniedpattern) !== false) {   // Match!
4044                 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4045             }
4046         }
4047     }
4049     return false;
4053 /// FILE HANDLING  /////////////////////////////////////////////
4056 /**
4057  * Makes an upload directory for a particular module.
4058  *
4059  * @uses $CFG
4060  * @param int $courseid The id of the course in question - maps to id field of 'course' table.
4061  * @return string|false Returns full path to directory if successful, false if not
4062  */
4063 function make_mod_upload_directory($courseid) {
4064     global $CFG;
4066     if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
4067         return false;
4068     }
4070     $strreadme = get_string('readme');
4072     if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
4073         copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4074     } else {
4075         copy($CFG->dirroot .'/lang/en/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4076     }
4077     return $moddata;
4080 /**
4081  * Returns current name of file on disk if it exists.
4082  *
4083  * @param string $newfile File to be verified
4084  * @return string Current name of file on disk if true
4085  */
4086 function valid_uploaded_file($newfile) {
4087     if (empty($newfile)) {
4088         return '';
4089     }
4090     if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
4091         return $newfile['tmp_name'];
4092     } else {
4093         return '';
4094     }
4097 /**
4098  * Returns the maximum size for uploading files.
4099  *
4100  * There are seven possible upload limits:
4101  * 1. in Apache using LimitRequestBody (no way of checking or changing this)
4102  * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
4103  * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
4104  * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
4105  * 5. by the Moodle admin in $CFG->maxbytes
4106  * 6. by the teacher in the current course $course->maxbytes
4107  * 7. by the teacher for the current module, eg $assignment->maxbytes
4108  *
4109  * These last two are passed to this function as arguments (in bytes).
4110  * Anything defined as 0 is ignored.
4111  * The smallest of all the non-zero numbers is returned.
4112  *
4113  * @param int $sizebytes ?
4114  * @param int $coursebytes Current course $course->maxbytes (in bytes)
4115  * @param int $modulebytes Current module ->maxbytes (in bytes)
4116  * @return int The maximum size for uploading files.
4117  * @todo Finish documenting this function
4118  */
4119 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4121     if (! $filesize = ini_get('upload_max_filesize')) {
4122         $filesize = '5M';
4123     }
4124     $minimumsize = get_real_size($filesize);
4126     if ($postsize = ini_get('post_max_size')) {
4127         $postsize = get_real_size($postsize);
4128         if ($postsize < $minimumsize) {
4129             $minimumsize = $postsize;
4130         }
4131     }
4133     if ($sitebytes and $sitebytes < $minimumsize) {
4134         $minimumsize = $sitebytes;
4135     }
4137     if ($coursebytes and $coursebytes < $minimumsize) {
4138         $minimumsize = $coursebytes;
4139     }
4141     if ($modulebytes and $modulebytes < $minimumsize) {
4142         $minimumsize = $modulebytes;
4143     }
4145     return $minimumsize;
4148 /**
4149  * Related to {@link get_max_upload_file_size()} - this function returns an
4150  * array of possible sizes in an array, translated to the
4151  * local language.
4152  *
4153  * @uses SORT_NUMERIC
4154  * @param int $sizebytes ?
4155  * @param int $coursebytes Current course $course->maxbytes (in bytes)
4156  * @param int $modulebytes Current module ->maxbytes (in bytes)
4157  * @return int
4158  * @todo Finish documenting this function
4159  */
4160 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4162     if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
4163         return array();
4164     }
4166     $filesize[$maxsize] = display_size($maxsize);
4168     $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
4169                       5242880, 10485760, 20971520, 52428800, 104857600);
4171     foreach ($sizelist as $sizebytes) {
4172        if ($sizebytes < $maxsize) {
4173            $filesize[$sizebytes] = display_size($sizebytes);
4174        }
4175     }
4177     krsort($filesize, SORT_NUMERIC);
4179     return $filesize;
4182 /**
4183  * If there has been an error uploading a file, print the appropriate error message
4184  * Numerical constants used as constant definitions not added until PHP version 4.2.0
4185  *
4186  * $filearray is a 1-dimensional sub-array of the $_FILES array
4187  * eg $filearray = $_FILES['userfile1']
4188  * If left empty then the first element of the $_FILES array will be used
4189  *
4190  * @uses $_FILES
4191  * @param array $filearray  A 1-dimensional sub-array of the $_FILES array
4192  * @param bool $returnerror If true then a string error message will be returned. Otherwise the user will be notified of the error in a notify() call.
4193  * @return bool|string
4194  */
4195 function print_file_upload_error($filearray = '', $returnerror = false) {
4197     if ($filearray == '' or !isset($filearray['error'])) {
4199         if (empty($_FILES)) return false;
4201         $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
4202         $filearray = array_shift($files); /// use first element of array
4203     }
4205     switch ($filearray['error']) {
4207         case 0: // UPLOAD_ERR_OK
4208             if ($filearray['size'] > 0) {
4209                 $errmessage = get_string('uploadproblem', $filearray['name']);
4210             } else {
4211                 $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
4212             }
4213             break;
4215         case 1: // UPLOAD_ERR_INI_SIZE
4216             $errmessage = get_string('uploadserverlimit');
4217             break;
4219         case 2: // UPLOAD_ERR_FORM_SIZE
4220             $errmessage = get_string('uploadformlimit');
4221             break;
4223         case 3: // UPLOAD_ERR_PARTIAL
4224             $errmessage = get_string('uploadpartialfile');
4225             break;
4227         case 4: // UPLOAD_ERR_NO_FILE
4228             $errmessage = get_string('uploadnofilefound');
4229             break;
4231         default:
4232             $errmessage = get_string('uploadproblem', $filearray['name']);
4233     }
4235     if ($returnerror) {
4236         return $errmessage;
4237     } else {
4238         notify($errmessage);
4239         return true;
4240     }
4244 /**
4245  * handy function to loop through an array of files and resolve any filename conflicts
4246  * both in the array of filenames and for what is already on disk.
4247  * not really compatible with the similar function in uploadlib.php
4248  * but this could be used for files/index.php for moving files around.
4249  */
4251 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
4252     foreach ($files as $k => $f) {
4253         if (check_potential_filename($destination,$f,$files)) {
4254             $bits = explode('.', $f);
4255             for ($i = 1; true; $i++) {
4256                 $try = sprintf($format, $bits[0], $i, $bits[1]);
4257                 if (!check_potential_filename($destination,$try,$files)) {
4258                     $files[$k] = $try;
4259                     break;
4260                 }
4261             }
4262         }
4263     }
4264     return $files;
4267 /**
4268  * @used by resolve_filename_collisions
4269  */
4270 function check_potential_filename($destination,$filename,$files) {
4271     if (file_exists($destination.'/'.$filename)) {
4272         return true;
4273     }
4274     if (count(array_keys($files,$filename)) > 1) {
4275         return true;
4276     }
4277     return false;
4281 /**
4282  * Returns an array with all the filenames in
4283  * all subdirectories, relative to the given rootdir.
4284  * If excludefile is defined, then that file/directory is ignored
4285  * If getdirs is true, then (sub)directories are included in the output
4286  * If getfiles is true, then files are included in the output
4287  * (at least one of these must be true!)
4288  *
4289  * @param string $rootdir  ?
4290  * @param string $excludefile  If defined then the specified file/directory is ignored
4291  * @param bool $descend  ?
4292  * @param bool $getdirs  If true then (sub)directories are included in the output
4293  * @param bool $getfiles  If true then files are included in the output
4294  * @return array An array with all the filenames in
4295  * all subdirectories, relative to the given rootdir
4296  * @todo Finish documenting this function. Add examples of $excludefile usage.
4297  */
4298 function get_directory_list($rootdir, $excludefile='', $descend=true, $getdirs=false, $getfiles=true) {
4300     $dirs = array();
4302     if (!$getdirs and !$getfiles) {   // Nothing to show
4303         return $dirs;
4304     }
4306     if (!is_dir($rootdir)) {          // Must be a directory
4307         return $dirs;
4308     }
4310     if (!$dir = opendir($rootdir)) {  // Can't open it for some reason
4311         return $dirs;
4312     }
4314     while (false !== ($file = readdir($dir))) {
4315         $firstchar = substr($file, 0, 1);
4316         if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
4317             continue;
4318         }
4319         $fullfile = $rootdir .'/'. $file;
4320         if (filetype($fullfile) == 'dir') {
4321             if ($getdirs) {
4322                 $dirs[] = $file;
4323             }
4324             if ($descend) {
4325                 $subdirs = get_directory_list($fullfile, $excludefile, $descend, $getdirs, $getfiles);
4326                 foreach ($subdirs as $subdir) {
4327                     $dirs[] = $file .'/'. $subdir;
4328                 }
4329             }
4330         } else if ($getfiles) {
4331             $dirs[] = $file;
4332         }
4333     }
4334     closedir($dir);
4336     asort($dirs);
4338     return $dirs;
4342 /**
4343  * Adds up all the files in a directory and works out the size.
4344  *
4345  * @param string $rootdir  ?
4346  * @param string $excludefile  ?
4347  * @return array
4348  * @todo Finish documenting this function
4349  */
4350 function get_directory_size($rootdir, $excludefile='') {
4352     global $CFG;
4354     // do it this way if we can, it's much faster
4355     if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
4356         $command = trim($CFG->pathtodu).' -sk --apparent-size '.escapeshellarg($rootdir);
4357         exec($command,$output,$return);
4358         if (is_array($output)) {
4359             return get_real_size(intval($output[0]).'k'); // we told it to return k.
4360         }
4361     }
4363     $size = 0;
4365     if (!is_dir($rootdir)) {          // Must be a directory
4366         return $dirs;
4367     }
4369     if (!$dir = @opendir($rootdir)) {  // Can't open it for some reason
4370         return $dirs;
4371     }
4373     while (false !== ($file = readdir($dir))) {
4374         $firstchar = substr($file, 0, 1);
4375         if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
4376             continue;
4377         }
4378         $fullfile = $rootdir .'/'. $file;
4379         if (filetype($fullfile) == 'dir') {
4380             $size += get_directory_size($fullfile, $excludefile);
4381         } else {
4382             $size += filesize($fullfile);
4383         }
4384     }
4385     closedir($dir);
4387     return $size;
4390 /**
4391  * Converts numbers like 10M into bytes.
4392  *
4393  * @param mixed $size The size to be converted
4394  * @return mixed
4395  */
4396 function get_real_size($size=0) {
4397     if (!$size) {
4398         return 0;
4399     }
4400     $scan['MB'] = 1048576;
4401     $scan['Mb'] = 1048576;
4402     $scan['M'] = 1048576;
4403     $scan['m'] = 1048576;
4404     $scan['KB'] = 1024;
4405     $scan['Kb'] = 1024;
4406     $scan['K'] = 1024;
4407     $scan['k'] = 1024;
4409     while (list($key) = each($scan)) {
4410         if ((strlen($size)>strlen($key))&&(substr($size, strlen($size) - strlen($key))==$key)) {
4411             $size = substr($size, 0, strlen($size) - strlen($key)) * $scan[$key];
4412             break;
4413         }
4414     }
4415     return $size;
4418 /**
4419  * Converts bytes into display form
4420  *
4421  * @param string $size  ?
4422  * @return string
4423  * @staticvar string $gb Localized string for size in gigabytes
4424  * @staticvar string $mb Localized string for size in megabytes
4425  * @staticvar string $kb Localized string for size in kilobytes
4426  * @staticvar string $b Localized string for size in bytes
4427  * @todo Finish documenting this function. Verify return type.
4428  */
4429 function display_size($size) {
4431     static $gb, $mb, $kb, $b;
4433     if (empty($gb)) {
4434         $gb = get_string('sizegb');
4435         $mb = get_string('sizemb');
4436         $kb = get_string('sizekb');
4437         $b  = get_string('sizeb');
4438     }
4440     if ($size >= 1073741824) {
4441         $size = round($size / 1073741824 * 10) / 10 . $gb;
4442     } else if ($size >= 1048576) {
4443         $size = round($size / 1048576 *&