Merged extension to check_browser_version from MDL-8417 (Nick)
[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 year
64  */
66 define('YEARSECS', 31536000);
68 /**
69  * Time constant - the number of seconds in a week
70  */
71 define('WEEKSECS', 604800);
73 /**
74  * Time constant - the number of seconds in a day
75  */
76 define('DAYSECS', 86400);
78 /**
79  * Time constant - the number of seconds in an hour
80  */
81 define('HOURSECS', 3600);
83 /**
84  * Time constant - the number of seconds in a minute
85  */
86 define('MINSECS', 60);
88 /**
89  * Time constant - the number of minutes in a day
90  */
91 define('DAYMINS', 1440);
93 /**
94  * Time constant - the number of minutes in an hour
95  */
96 define('HOURMINS', 60);
98 /// Parameter constants - every call to optional_param(), required_param()  ///
99 /// or clean_param() should have a specified type of parameter.  //////////////
101 /**
102  * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
103  * originally was 0, but changed because we need to detect unknown
104  * parameter types and swiched order in clean_param().
105  */
106 define('PARAM_RAW', 666);
108 /**
109  * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
110  * It was one of the first types, that is why it is abused so much ;-)
111  */
112 define('PARAM_CLEAN',    0x0001);
114 /**
115  * PARAM_INT - integers only, use when expecting only numbers.
116  */
117 define('PARAM_INT',      0x0002);
119 /**
120  * PARAM_INTEGER - an alias for PARAM_INT
121  */
122 define('PARAM_INTEGER',  0x0002);
124 /**
125  * PARAM_NUMBER - a real/floating point number. 
126  */
127 define('PARAM_NUMBER',  0x000a);
129 /**
130  * PARAM_ALPHA - contains only english letters.
131  */
132 define('PARAM_ALPHA',    0x0004);
134 /**
135  * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
136  * @TODO: should we alias it to PARAM_ALPHANUM ?
137  */
138 define('PARAM_ACTION',   0x0004);
140 /**
141  * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
142  * @TODO: should we alias it to PARAM_ALPHANUM ?
143  */
144 define('PARAM_FORMAT',   0x0004);
146 /**
147  * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
148  */
149 define('PARAM_NOTAGS',   0x0008);
151  /**
152  * PARAM_MULTILANG - alias of PARAM_TEXT.
153  */
154 define('PARAM_MULTILANG',  0x0009);
156  /**
157  * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
158  */
159 define('PARAM_TEXT',  0x0009);
161 /**
162  * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
163  */
164 define('PARAM_FILE',     0x0010);
166 /**
167  * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
168  * note: the leading slash is not removed, window drive letter is not allowed
169  */
170 define('PARAM_PATH',     0x0020);
172 /**
173  * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
174  */
175 define('PARAM_HOST',     0x0040);
177 /**
178  * PARAM_URL - expected properly formatted URL.
179  */
180 define('PARAM_URL',      0x0080);
182 /**
183  * 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!)
184  */
185 define('PARAM_LOCALURL', 0x0180);
187 /**
188  * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
189  * use when you want to store a new file submitted by students
190  */
191 define('PARAM_CLEANFILE',0x0200);
193 /**
194  * PARAM_ALPHANUM - expected numbers and letters only.
195  */
196 define('PARAM_ALPHANUM', 0x0400);
198 /**
199  * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
200  */
201 define('PARAM_BOOL',     0x0800);
203 /**
204  * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
205  * note: do not forget to addslashes() before storing into database!
206  */
207 define('PARAM_CLEANHTML',0x1000);
209 /**
210  * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
211  * suitable for include() and require()
212  * @TODO: should we rename this function to PARAM_SAFEDIRS??
213  */
214 define('PARAM_ALPHAEXT', 0x2000);
216 /**
217  * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
218  */
219 define('PARAM_SAFEDIR',  0x4000);
221 /**
222  * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9.  Numbers and comma only.
223  */
224 define('PARAM_SEQUENCE',  0x8000);
226 /**
227  * PARAM_PEM - Privacy Enhanced Mail format
228  */
229 define('PARAM_PEM',      0x10000);
231 /**
232  * PARAM_BASE64 - Base 64 encoded format
233  */
234 define('PARAM_BASE64',   0x20000);
237 /// Page types ///
238 /**
239  * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
240  */
241 define('PAGE_COURSE_VIEW', 'course-view');
243 /// Debug levels ///
244 /** no warnings at all */
245 define ('DEBUG_NONE', 0);
246 /** E_ERROR | E_PARSE */
247 define ('DEBUG_MINIMAL', 5);
248 /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
249 define ('DEBUG_NORMAL', 15);
250 /** E_ALL without E_STRICT and E_RECOVERABLE_ERROR for now */
251 define ('DEBUG_ALL', 2047);
252 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
253 define ('DEBUG_DEVELOPER', 34815);
255 /**
256  * Blog access level constant declaration
257  */
258 define ('BLOG_USER_LEVEL', 1);
259 define ('BLOG_GROUP_LEVEL', 2);
260 define ('BLOG_COURSE_LEVEL', 3);
261 define ('BLOG_SITE_LEVEL', 4);
262 define ('BLOG_GLOBAL_LEVEL', 5);
264 /**
265  * Authentication - error codes for user confirm
266  */
267 define('AUTH_CONFIRM_FAIL', 0);
268 define('AUTH_CONFIRM_OK', 1);
269 define('AUTH_CONFIRM_ALREADY', 2);
270 define('AUTH_CONFIRM_ERROR', 3);
274 /// PARAMETER HANDLING ////////////////////////////////////////////////////
276 /**
277  * Returns a particular value for the named variable, taken from
278  * POST or GET.  If the parameter doesn't exist then an error is
279  * thrown because we require this variable.
280  *
281  * This function should be used to initialise all required values
282  * in a script that are based on parameters.  Usually it will be
283  * used like this:
284  *    $id = required_param('id');
285  *
286  * @param string $parname the name of the page parameter we want
287  * @param int $type expected type of parameter
288  * @return mixed
289  */
290 function required_param($parname, $type=PARAM_CLEAN) {
292     // detect_unchecked_vars addition
293     global $CFG;
294     if (!empty($CFG->detect_unchecked_vars)) {
295         global $UNCHECKED_VARS;
296         unset ($UNCHECKED_VARS->vars[$parname]);
297     }
299     if (isset($_POST[$parname])) {       // POST has precedence
300         $param = $_POST[$parname];
301     } else if (isset($_GET[$parname])) {
302         $param = $_GET[$parname];
303     } else {
304         error('A required parameter ('.$parname.') was missing');
305     }
307     return clean_param($param, $type);
310 /**
311  * Returns a particular value for the named variable, taken from
312  * POST or GET, otherwise returning a given default.
313  *
314  * This function should be used to initialise all optional values
315  * in a script that are based on parameters.  Usually it will be
316  * used like this:
317  *    $name = optional_param('name', 'Fred');
318  *
319  * @param string $parname the name of the page parameter we want
320  * @param mixed  $default the default value to return if nothing is found
321  * @param int $type expected type of parameter
322  * @return mixed
323  */
324 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
326     // detect_unchecked_vars addition
327     global $CFG;
328     if (!empty($CFG->detect_unchecked_vars)) {
329         global $UNCHECKED_VARS;
330         unset ($UNCHECKED_VARS->vars[$parname]);
331     }
333     if (isset($_POST[$parname])) {       // POST has precedence
334         $param = $_POST[$parname];
335     } else if (isset($_GET[$parname])) {
336         $param = $_GET[$parname];
337     } else {
338         return $default;
339     }
341     return clean_param($param, $type);
344 /**
345  * Used by {@link optional_param()} and {@link required_param()} to
346  * clean the variables and/or cast to specific types, based on
347  * an options field.
348  * <code>
349  * $course->format = clean_param($course->format, PARAM_ALPHA);
350  * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
351  * </code>
352  *
353  * @uses $CFG
354  * @uses PARAM_CLEAN
355  * @uses PARAM_INT
356  * @uses PARAM_INTEGER
357  * @uses PARAM_ALPHA
358  * @uses PARAM_ALPHANUM
359  * @uses PARAM_NOTAGS
360  * @uses PARAM_ALPHAEXT
361  * @uses PARAM_BOOL
362  * @uses PARAM_SAFEDIR
363  * @uses PARAM_CLEANFILE
364  * @uses PARAM_FILE
365  * @uses PARAM_PATH
366  * @uses PARAM_HOST
367  * @uses PARAM_URL
368  * @uses PARAM_LOCALURL
369  * @uses PARAM_CLEANHTML
370  * @uses PARAM_SEQUENCE
371  * @param mixed $param the variable we are cleaning
372  * @param int $type expected format of param after cleaning.
373  * @return mixed
374  */
375 function clean_param($param, $type) {
377     global $CFG;
379     if (is_array($param)) {              // Let's loop
380         $newparam = array();
381         foreach ($param as $key => $value) {
382             $newparam[$key] = clean_param($value, $type);
383         }
384         return $newparam;
385     }
387     switch ($type) {
388         case PARAM_RAW:          // no cleaning at all
389             return $param;
391         case PARAM_CLEAN:        // General HTML cleaning, try to use more specific type if possible
392             if (is_numeric($param)) {
393                 return $param;
394             }
395             $param = stripslashes($param);   // Needed for kses to work fine
396             $param = clean_text($param);     // Sweep for scripts, etc
397             return addslashes($param);       // Restore original request parameter slashes
399         case PARAM_CLEANHTML:    // prepare html fragment for display, do not store it into db!!
400             $param = stripslashes($param);   // Remove any slashes
401             $param = clean_text($param);     // Sweep for scripts, etc
402             return trim($param);
404         case PARAM_INT:
405             return (int)$param;  // Convert to integer
407         case PARAM_NUMBER:
408             return (float)$param;  // Convert to integer
410         case PARAM_ALPHA:        // Remove everything not a-z
411             return eregi_replace('[^a-zA-Z]', '', $param);
413         case PARAM_ALPHANUM:     // Remove everything not a-zA-Z0-9
414             return eregi_replace('[^A-Za-z0-9]', '', $param);
416         case PARAM_ALPHAEXT:     // Remove everything not a-zA-Z/_-
417             return eregi_replace('[^a-zA-Z/_-]', '', $param);
419         case PARAM_SEQUENCE:     // Remove everything not 0-9,
420             return eregi_replace('[^0-9,]', '', $param);
422         case PARAM_BOOL:         // Convert to 1 or 0
423             $tempstr = strtolower($param);
424             if ($tempstr == 'on' or $tempstr == 'yes' ) {
425                 $param = 1;
426             } else if ($tempstr == 'off' or $tempstr == 'no') {
427                 $param = 0;
428             } else {
429                 $param = empty($param) ? 0 : 1;
430             }
431             return $param;
433         case PARAM_NOTAGS:       // Strip all tags
434             return strip_tags($param);
436         case PARAM_TEXT:    // leave only tags needed for multilang
437             return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
439         case PARAM_SAFEDIR:      // Remove everything not a-zA-Z0-9_-
440             return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
442         case PARAM_CLEANFILE:    // allow only safe characters
443             return clean_filename($param);
445         case PARAM_FILE:         // Strip all suspicious characters from filename
446             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
447             $param = ereg_replace('\.\.+', '', $param);
448             if($param == '.') {
449                 $param = '';
450             }
451             return $param;
453         case PARAM_PATH:         // Strip all suspicious characters from file path
454             $param = str_replace('\\\'', '\'', $param);
455             $param = str_replace('\\"', '"', $param);
456             $param = str_replace('\\', '/', $param);
457             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
458             $param = ereg_replace('\.\.+', '', $param);
459             $param = ereg_replace('//+', '/', $param);
460             return ereg_replace('/(\./)+', '/', $param);
462         case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
463             preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
464             // match ipv4 dotted quad
465             if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
466                 // confirm values are ok
467                 if ( $match[0] > 255
468                      || $match[1] > 255
469                      || $match[3] > 255
470                      || $match[4] > 255 ) {
471                     // hmmm, what kind of dotted quad is this?
472                     $param = '';
473                 }
474             } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
475                        && !preg_match('/^[\.-]/',  $param) // no leading dots/hyphens
476                        && !preg_match('/[\.-]$/',  $param) // no trailing dots/hyphens
477                        ) {
478                 // all is ok - $param is respected
479             } else {
480                 // all is not ok...
481                 $param='';
482             }
483             return $param;
485         case PARAM_URL:          // allow safe ftp, http, mailto urls
486             include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
487             if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
488                 // all is ok, param is respected
489             } else {
490                 $param =''; // not really ok
491             }
492             return $param;
494         case PARAM_LOCALURL:     // allow http absolute, root relative and relative URLs within wwwroot
495             clean_param($param, PARAM_URL);
496             if (!empty($param)) {
497                 if (preg_match(':^/:', $param)) {
498                     // root-relative, ok!
499                 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
500                     // absolute, and matches our wwwroot
501                 } else {
502                     // relative - let's make sure there are no tricks
503                     if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
504                         // looks ok.
505                     } else {
506                         $param = '';
507                     }
508                 }
509             }
510             return $param;
511         case PARAM_PEM:
512             $param = trim($param);
513             // PEM formatted strings may contain letters/numbers and the symbols
514             // forward slash: /
515             // plus sign:     +
516             // equal sign:    =
517             // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
518             if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
519                 list($wholething, $body) = $matches;
520                 unset($wholething, $matches);
521                 $b64 = clean_param($body, PARAM_BASE64);
522                 if (!empty($b64)) {
523                     return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
524                 } else {
525                     return '';
526                 }
527             }
528             return '';
529         case PARAM_BASE64:
530             if (!empty($param)) {
531                 // PEM formatted strings may contain letters/numbers and the symbols
532                 // forward slash: /
533                 // plus sign:     +
534                 // equal sign:    =
535                 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
536                     return '';
537                 }
538                 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
539                 // Each line of base64 encoded data must be 64 characters in
540                 // length, except for the last line which may be less than (or
541                 // equal to) 64 characters long.
542                 for ($i=0, $j=count($lines); $i < $j; $i++) {
543                     if ($i + 1 == $j) {
544                         if (64 < strlen($lines[$i])) {
545                             return '';
546                         }
547                         continue;
548                     }
550                     if (64 != strlen($lines[$i])) {
551                         return '';
552                     }
553                 }
554                 return implode("\n",$lines);
555             } else {
556                 return '';
557             }
558         default:                 // throw error, switched parameters in optional_param or another serious problem
559             error("Unknown parameter type: $type");
560     }
565 /**
566  * Set a key in global configuration
567  *
568  * Set a key/value pair in both this session's {@link $CFG} global variable
569  * and in the 'config' database table for future sessions.
570  *
571  * Can also be used to update keys for plugin-scoped configs in config_plugin table.
572  * In that case it doesn't affect $CFG.
573  *
574  * @param string $name the key to set
575  * @param string $value the value to set (without magic quotes)
576  * @param string $plugin (optional) the plugin scope
577  * @uses $CFG
578  * @return bool
579  */
580 function set_config($name, $value, $plugin=NULL) {
581 /// No need for get_config because they are usually always available in $CFG
583     global $CFG;
585     if (empty($plugin)) {
586         $CFG->$name = $value;  // So it's defined for this invocation at least
588         if (get_field('config', 'name', 'name', $name)) {
589             return set_field('config', 'value', addslashes($value), 'name', $name);
590         } else {
591             $config = new object();
592             $config->name = $name;
593             $config->value = addslashes($value);
594             return insert_record('config', $config);
595         }
596     } else { // plugin scope
597         if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
598             return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
599         } else {
600             $config = new object();
601             $config->plugin = addslashes($plugin);
602             $config->name   = $name;
603             $config->value  = addslashes($value);
604             return insert_record('config_plugins', $config);
605         }
606     }
609 /**
610  * Get configuration values from the global config table
611  * or the config_plugins table.
612  *
613  * If called with no parameters it will do the right thing
614  * generating $CFG safely from the database without overwriting
615  * existing values.
616  *
617  * If called with 2 parameters it will return a $string single
618  * value or false of the value is not found.
619  *
620  * @param string $plugin
621  * @param string $name
622  * @uses $CFG
623  * @return hash-like object or single value
624  *
625  */
626 function get_config($plugin=NULL, $name=NULL) {
628     global $CFG;
630     if (!empty($name)) { // the user is asking for a specific value
631         if (!empty($plugin)) {
632             return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
633         } else {
634             return get_field('config', 'value', 'name', $name);
635         }
636     }
638     // the user is after a recordset
639     if (!empty($plugin)) {
640         if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
641             $configs = (array)$configs;
642             $localcfg = array();
643             foreach ($configs as $config) {
644                 $localcfg[$config->name] = $config->value;
645             }
646             return (object)$localcfg;
647         } else {
648             return false;
649         }
650     } else {
651         // this was originally in setup.php
652         if ($configs = get_records('config')) {
653             $localcfg = (array)$CFG;
654             foreach ($configs as $config) {
655                 if (!isset($localcfg[$config->name])) {
656                     $localcfg[$config->name] = $config->value;
657                 } else {
658                     if ($localcfg[$config->name] != $config->value ) {
659                         // complain if the DB has a different
660                         // value than config.php does
661                         error_log("\$CFG->{$config->name} in config.php ({$localcfg[$config->name]}) overrides database setting ({$config->value})");
662                     }
663                 }
664             }
666             $localcfg = (object)$localcfg;
667             return $localcfg;
668         } else {
669             // preserve $CFG if DB returns nothing or error
670             return $CFG;
671         }
673     }
676 /**
677  * Removes a key from global configuration
678  *
679  * @param string $name the key to set
680  * @param string $plugin (optional) the plugin scope
681  * @uses $CFG
682  * @return bool
683  */
684 function unset_config($name, $plugin=NULL) {
686     global $CFG;
688     unset($CFG->$name);
690     if (empty($plugin)) {
691         return delete_records('config', 'name', $name);
692     } else { 
693         return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
694     }
698 /**
699  * Refresh current $USER session global variable with all their current preferences.
700  * @uses $USER
701  */
702 function reload_user_preferences() {
704     global $USER;
706     if(empty($USER) || empty($USER->id)) {
707         return false;
708     }
710     unset($USER->preference);
712     if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
713         foreach ($preferences as $preference) {
714             $USER->preference[$preference->name] = $preference->value;
715         }
716     } else {
717             //return empty preference array to hold new values
718             $USER->preference = array();
719     }
722 /**
723  * Sets a preference for the current user
724  * Optionally, can set a preference for a different user object
725  * @uses $USER
726  * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
728  * @param string $name The key to set as preference for the specified user
729  * @param string $value The value to set forthe $name key in the specified user's record
730  * @param int $userid A moodle user ID
731  * @return bool
732  */
733 function set_user_preference($name, $value, $otheruser=NULL) {
735     global $USER;
737     if (empty($otheruser)){
738         if (!empty($USER) && !empty($USER->id)) {
739             $userid = $USER->id;
740         } else {
741             return false;
742         }
743     } else {
744         $userid = $otheruser;
745     }
747     if (empty($name)) {
748         return false;
749     }
751     if ($preference = get_record('user_preferences', 'userid', $userid, 'name', $name)) {
752         if (set_field('user_preferences', 'value', $value, 'id', $preference->id)) {
753             if ($userid == $USER->id) {
754                 $USER->preference[$name] = $value;
755             }
756             return true;
757         } else {
758             return false;
759         }
761     } else {
762         $preference->userid = $userid;
763         $preference->name   = $name;
764         $preference->value  = (string)$value;
765         if (insert_record('user_preferences', $preference)) {
766             if ($userid == $USER->id) {
767                 $USER->preference[$name] = $value;
768             }
769             return true;
770         } else {
771             return false;
772         }
773     }
776 /**
777  * Unsets a preference completely by deleting it from the database
778  * Optionally, can set a preference for a different user id
779  * @uses $USER
780  * @param string  $name The key to unset as preference for the specified user
781  * @param int $userid A moodle user ID
782  * @return bool
783  */
784 function unset_user_preference($name, $userid=NULL) {
786     global $USER;
788     if (empty($userid)){
789         if(!empty($USER) && !empty($USER->id)) {
790             $userid = $USER->id;
791         }
792         else {
793             return false;
794         }
795     }
797     //Delete the preference from $USER
798     if (isset($USER->preference[$name])) {
799         unset($USER->preference[$name]);
800     }
802     //Then from DB
803     return delete_records('user_preferences', 'userid', $userid, 'name', $name);
807 /**
808  * Sets a whole array of preferences for the current user
809  * @param array $prefarray An array of key/value pairs to be set
810  * @param int $userid A moodle user ID
811  * @return bool
812  */
813 function set_user_preferences($prefarray, $userid=NULL) {
815     global $USER;
817     if (!is_array($prefarray) or empty($prefarray)) {
818         return false;
819     }
821     if (empty($userid)){
822         if (!empty($USER) && !empty($USER->id)) {
823             $userid = NULL;  // Continue with the current user below
824         } else {
825             return false;    // No-one to set!
826         }
827     }
829     $return = true;
830     foreach ($prefarray as $name => $value) {
831         // The order is important; if the test for return is done first, then
832         // if one function call fails all the remaining ones will be "optimized away"
833         $return = set_user_preference($name, $value, $userid) and $return;
834     }
835     return $return;
838 /**
839  * If no arguments are supplied this function will return
840  * all of the current user preferences as an array.
841  * If a name is specified then this function
842  * attempts to return that particular preference value.  If
843  * none is found, then the optional value $default is returned,
844  * otherwise NULL.
845  * @param string $name Name of the key to use in finding a preference value
846  * @param string $default Value to be returned if the $name key is not set in the user preferences
847  * @param int $userid A moodle user ID
848  * @uses $USER
849  * @return string
850  */
851 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
853     global $USER;
855     if (empty($userid)) {   // assume current user
856         if (empty($USER->preference)) {
857             return $default;              // Default value (or NULL)
858         }
859         if (empty($name)) {
860             return $USER->preference;     // Whole array
861         }
862         if (!isset($USER->preference[$name])) {
863             return $default;              // Default value (or NULL)
864         }
865         return $USER->preference[$name];  // The single value
867     } else {
868         $preference = get_records_menu('user_preferences', 'userid', $userid, 'name', 'name,value');
870         if (empty($name)) {
871             return $preference;
872         }
873         if (!isset($preference[$name])) {
874             return $default;              // Default value (or NULL)
875         }
876         return $preference[$name];        // The single value
877     }
881 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
883 /**
884  * Given date parts in user time produce a GMT timestamp.
885  *
886  * @param int $year The year part to create timestamp of
887  * @param int $month The month part to create timestamp of
888  * @param int $day The day part to create timestamp of
889  * @param int $hour The hour part to create timestamp of
890  * @param int $minute The minute part to create timestamp of
891  * @param int $second The second part to create timestamp of
892  * @param float $timezone ?
893  * @param bool $applydst ?
894  * @return int timestamp
895  * @todo Finish documenting this function
896  */
897 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
899     $timezone = get_user_timezone_offset($timezone);
901     if (abs($timezone) > 13) {
902         $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
903     } else {
904         $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
905         $time = usertime($time, $timezone);
906         if($applydst) {
907             $time -= dst_offset_on($time);
908         }
909     }
911     return $time;
915 /**
916  * Given an amount of time in seconds, returns string
917  * formatted nicely as weeks, days, hours etc as needed
918  *
919  * @uses MINSECS
920  * @uses HOURSECS
921  * @uses DAYSECS
922  * @uses YEARSECS
923  * @param int $totalsecs ?
924  * @param array $str ?
925  * @return string
926  */
927  function format_time($totalsecs, $str=NULL) {
929     $totalsecs = abs($totalsecs);
931     if (!$str) {  // Create the str structure the slow way
932         $str->day   = get_string('day');
933         $str->days  = get_string('days');
934         $str->hour  = get_string('hour');
935         $str->hours = get_string('hours');
936         $str->min   = get_string('min');
937         $str->mins  = get_string('mins');
938         $str->sec   = get_string('sec');
939         $str->secs  = get_string('secs');
940         $str->year  = get_string('year');
941         $str->years = get_string('years');
942     }
945     $years     = floor($totalsecs/YEARSECS);
946     $remainder = $totalsecs - ($years*YEARSECS);
947     $days      = floor($remainder/DAYSECS);
948     $remainder = $totalsecs - ($days*DAYSECS);
949     $hours     = floor($remainder/HOURSECS);
950     $remainder = $remainder - ($hours*HOURSECS);
951     $mins      = floor($remainder/MINSECS);
952     $secs      = $remainder - ($mins*MINSECS);
954     $ss = ($secs == 1)  ? $str->sec  : $str->secs;
955     $sm = ($mins == 1)  ? $str->min  : $str->mins;
956     $sh = ($hours == 1) ? $str->hour : $str->hours;
957     $sd = ($days == 1)  ? $str->day  : $str->days;
958     $sy = ($years == 1)  ? $str->year  : $str->years;
960     $oyears = '';
961     $odays = '';
962     $ohours = '';
963     $omins = '';
964     $osecs = '';
966     if ($years)  $oyears  = $years .' '. $sy;
967     if ($days)  $odays  = $days .' '. $sd;
968     if ($hours) $ohours = $hours .' '. $sh;
969     if ($mins)  $omins  = $mins .' '. $sm;
970     if ($secs)  $osecs  = $secs .' '. $ss;
972     if ($years)  return $oyears .' '. $odays;
973     if ($days)  return $odays .' '. $ohours;
974     if ($hours) return $ohours .' '. $omins;
975     if ($mins)  return $omins .' '. $osecs;
976     if ($secs)  return $osecs;
977     return get_string('now');
980 /**
981  * Returns a formatted string that represents a date in user time
982  * <b>WARNING: note that the format is for strftime(), not date().</b>
983  * Because of a bug in most Windows time libraries, we can't use
984  * the nicer %e, so we have to use %d which has leading zeroes.
985  * A lot of the fuss in the function is just getting rid of these leading
986  * zeroes as efficiently as possible.
987  *
988  * If parameter fixday = true (default), then take off leading
989  * zero from %d, else mantain it.
990  *
991  * @uses HOURSECS
992  * @param  int $date timestamp in GMT
993  * @param string $format strftime format
994  * @param float $timezone
995  * @param bool $fixday If true (default) then the leading
996  * zero from %d is removed. If false then the leading zero is mantained.
997  * @return string
998  */
999 function userdate($date, $format='', $timezone=99, $fixday = true) {
1001     global $CFG;
1003     static $strftimedaydatetime;
1005     if ($format == '') {
1006         if (empty($strftimedaydatetime)) {
1007             $strftimedaydatetime = get_string('strftimedaydatetime');
1008         }
1009         $format = $strftimedaydatetime;
1010     }
1012     if (!empty($CFG->nofixday)) {  // Config.php can force %d not to be fixed.
1013         $fixday = false;
1014     } else if ($fixday) {
1015         $formatnoday = str_replace('%d', 'DD', $format);
1016         $fixday = ($formatnoday != $format);
1017     }
1019     $date += dst_offset_on($date);
1021     $timezone = get_user_timezone_offset($timezone);
1023     if (abs($timezone) > 13) {   /// Server time
1024         if ($fixday) {
1025             $datestring = strftime($formatnoday, $date);
1026             $daystring  = str_replace(' 0', '', strftime(' %d', $date));
1027             $datestring = str_replace('DD', $daystring, $datestring);
1028         } else {
1029             $datestring = strftime($format, $date);
1030         }
1031     } else {
1032         $date += (int)($timezone * 3600);
1033         if ($fixday) {
1034             $datestring = gmstrftime($formatnoday, $date);
1035             $daystring  = str_replace(' 0', '', gmstrftime(' %d', $date));
1036             $datestring = str_replace('DD', $daystring, $datestring);
1037         } else {
1038             $datestring = gmstrftime($format, $date);
1039         }
1040     }
1042 /// If we are running under Windows convert from windows encoding to UTF-8
1043 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1045    if ($CFG->ostype == 'WINDOWS') {
1046        if ($localewincharset = get_string('localewincharset')) {
1047            $textlib = textlib_get_instance();
1048            $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1049        }
1050    }
1052     return $datestring;
1055 /**
1056  * Given a $time timestamp in GMT (seconds since epoch),
1057  * returns an array that represents the date in user time
1058  *
1059  * @uses HOURSECS
1060  * @param int $time Timestamp in GMT
1061  * @param float $timezone ?
1062  * @return array An array that represents the date in user time
1063  * @todo Finish documenting this function
1064  */
1065 function usergetdate($time, $timezone=99) {
1067     $timezone = get_user_timezone_offset($timezone);
1069     if (abs($timezone) > 13) {    // Server time
1070         return getdate($time);
1071     }
1073     // There is no gmgetdate so we use gmdate instead
1074     $time += dst_offset_on($time);
1075     $time += intval((float)$timezone * HOURSECS);
1077     $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
1079     list(
1080         $getdate['seconds'],
1081         $getdate['minutes'],
1082         $getdate['hours'],
1083         $getdate['mday'],
1084         $getdate['mon'],
1085         $getdate['year'],
1086         $getdate['wday'],
1087         $getdate['yday'],
1088         $getdate['weekday'],
1089         $getdate['month']
1090     ) = explode('_', $datestring);
1092     return $getdate;
1095 /**
1096  * Given a GMT timestamp (seconds since epoch), offsets it by
1097  * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1098  *
1099  * @uses HOURSECS
1100  * @param  int $date Timestamp in GMT
1101  * @param float $timezone
1102  * @return int
1103  */
1104 function usertime($date, $timezone=99) {
1106     $timezone = get_user_timezone_offset($timezone);
1108     if (abs($timezone) > 13) {
1109         return $date;
1110     }
1111     return $date - (int)($timezone * HOURSECS);
1114 /**
1115  * Given a time, return the GMT timestamp of the most recent midnight
1116  * for the current user.
1117  *
1118  * @param int $date Timestamp in GMT
1119  * @param float $timezone ?
1120  * @return ?
1121  */
1122 function usergetmidnight($date, $timezone=99) {
1124     $timezone = get_user_timezone_offset($timezone);
1125     $userdate = usergetdate($date, $timezone);
1127     // Time of midnight of this user's day, in GMT
1128     return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1132 /**
1133  * Returns a string that prints the user's timezone
1134  *
1135  * @param float $timezone The user's timezone
1136  * @return string
1137  */
1138 function usertimezone($timezone=99) {
1140     $tz = get_user_timezone($timezone);
1142     if (!is_float($tz)) {
1143         return $tz;
1144     }
1146     if(abs($tz) > 13) { // Server time
1147         return get_string('serverlocaltime');
1148     }
1150     if($tz == intval($tz)) {
1151         // Don't show .0 for whole hours
1152         $tz = intval($tz);
1153     }
1155     if($tz == 0) {
1156         return 'GMT';
1157     }
1158     else if($tz > 0) {
1159         return 'GMT+'.$tz;
1160     }
1161     else {
1162         return 'GMT'.$tz;
1163     }
1167 /**
1168  * Returns a float which represents the user's timezone difference from GMT in hours
1169  * Checks various settings and picks the most dominant of those which have a value
1170  *
1171  * @uses $CFG
1172  * @uses $USER
1173  * @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
1174  * @return int
1175  */
1176 function get_user_timezone_offset($tz = 99) {
1178     global $USER, $CFG;
1180     $tz = get_user_timezone($tz);
1182     if (is_float($tz)) {
1183         return $tz;
1184     } else {
1185         $tzrecord = get_timezone_record($tz);
1186         if (empty($tzrecord)) {
1187             return 99.0;
1188         }
1189         return (float)$tzrecord->gmtoff / HOURMINS;
1190     }
1193 /**
1194  * Returns a float or a string which denotes the user's timezone
1195  * 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)
1196  * means that for this timezone there are also DST rules to be taken into account
1197  * Checks various settings and picks the most dominant of those which have a value
1198  *
1199  * @uses $USER
1200  * @uses $CFG
1201  * @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
1202  * @return mixed
1203  */
1204 function get_user_timezone($tz = 99) {
1205     global $USER, $CFG;
1207     $timezones = array(
1208         $tz,
1209         isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1210         isset($USER->timezone) ? $USER->timezone : 99,
1211         isset($CFG->timezone) ? $CFG->timezone : 99,
1212         );
1214     $tz = 99;
1216     while(($tz == '' || $tz == 99) && $next = each($timezones)) {
1217         $tz = $next['value'];
1218     }
1220     return is_numeric($tz) ? (float) $tz : $tz;
1223 /**
1224  * ?
1225  *
1226  * @uses $CFG
1227  * @uses $db
1228  * @param string $timezonename ?
1229  * @return object
1230  */
1231 function get_timezone_record($timezonename) {
1232     global $CFG, $db;
1233     static $cache = NULL;
1235     if ($cache === NULL) {
1236         $cache = array();
1237     }
1239     if (isset($cache[$timezonename])) {
1240         return $cache[$timezonename];
1241     }
1243     return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1244                                       WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1247 /**
1248  * ?
1249  *
1250  * @uses $CFG
1251  * @uses $USER
1252  * @param ? $fromyear ?
1253  * @param ? $to_year ?
1254  * @return bool
1255  */
1256 function calculate_user_dst_table($from_year = NULL, $to_year = NULL) {
1257     global $CFG, $SESSION;
1259     $usertz = get_user_timezone();
1261     if (is_float($usertz)) {
1262         // Trivial timezone, no DST
1263         return false;
1264     }
1266     if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1267         // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1268         unset($SESSION->dst_offsets);
1269         unset($SESSION->dst_range);
1270     }
1272     if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1273         // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1274         // This will be the return path most of the time, pretty light computationally
1275         return true;
1276     }
1278     // Reaching here means we either need to extend our table or create it from scratch
1280     // Remember which TZ we calculated these changes for
1281     $SESSION->dst_offsettz = $usertz;
1283     if(empty($SESSION->dst_offsets)) {
1284         // If we 're creating from scratch, put the two guard elements in there
1285         $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1286     }
1287     if(empty($SESSION->dst_range)) {
1288         // If creating from scratch
1289         $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1290         $to   = min((empty($to_year)   ? intval(date('Y')) + 3 : $to_year),   2035);
1292         // Fill in the array with the extra years we need to process
1293         $yearstoprocess = array();
1294         for($i = $from; $i <= $to; ++$i) {
1295             $yearstoprocess[] = $i;
1296         }
1298         // Take note of which years we have processed for future calls
1299         $SESSION->dst_range = array($from, $to);
1300     }
1301     else {
1302         // If needing to extend the table, do the same
1303         $yearstoprocess = array();
1305         $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1306         $to   = min((empty($to_year)   ? $SESSION->dst_range[1] : $to_year),   2035);
1308         if($from < $SESSION->dst_range[0]) {
1309             // Take note of which years we need to process and then note that we have processed them for future calls
1310             for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1311                 $yearstoprocess[] = $i;
1312             }
1313             $SESSION->dst_range[0] = $from;
1314         }
1315         if($to > $SESSION->dst_range[1]) {
1316             // Take note of which years we need to process and then note that we have processed them for future calls
1317             for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1318                 $yearstoprocess[] = $i;
1319             }
1320             $SESSION->dst_range[1] = $to;
1321         }
1322     }
1324     if(empty($yearstoprocess)) {
1325         // This means that there was a call requesting a SMALLER range than we have already calculated
1326         return true;
1327     }
1329     // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1330     // Also, the array is sorted in descending timestamp order!
1332     // Get DB data
1333     $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');
1334     if(empty($presetrecords)) {
1335         return false;
1336     }
1338     // Remove ending guard (first element of the array)
1339     reset($SESSION->dst_offsets);
1340     unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1342     // Add all required change timestamps
1343     foreach($yearstoprocess as $y) {
1344         // Find the record which is in effect for the year $y
1345         foreach($presetrecords as $year => $preset) {
1346             if($year <= $y) {
1347                 break;
1348             }
1349         }
1351         $changes = dst_changes_for_year($y, $preset);
1353         if($changes === NULL) {
1354             continue;
1355         }
1356         if($changes['dst'] != 0) {
1357             $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1358         }
1359         if($changes['std'] != 0) {
1360             $SESSION->dst_offsets[$changes['std']] = 0;
1361         }
1362     }
1364     // Put in a guard element at the top
1365     $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1366     $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1368     // Sort again
1369     krsort($SESSION->dst_offsets);
1371     return true;
1374 function dst_changes_for_year($year, $timezone) {
1376     if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1377         return NULL;
1378     }
1380     $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1381     $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1383     list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1384     list($std_hour, $std_min) = explode(':', $timezone->std_time);
1386     $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1387     $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1389     // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1390     // This has the advantage of being able to have negative values for hour, i.e. for timezones
1391     // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1393     $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1394     $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1396     return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1399 // $time must NOT be compensated at all, it has to be a pure timestamp
1400 function dst_offset_on($time) {
1401     global $SESSION;
1403     if(!calculate_user_dst_table() || empty($SESSION->dst_offsets)) {
1404         return 0;
1405     }
1407     reset($SESSION->dst_offsets);
1408     while(list($from, $offset) = each($SESSION->dst_offsets)) {
1409         if($from <= $time) {
1410             break;
1411         }
1412     }
1414     // This is the normal return path
1415     if($offset !== NULL) {
1416         return $offset;
1417     }
1419     // Reaching this point means we haven't calculated far enough, do it now:
1420     // Calculate extra DST changes if needed and recurse. The recursion always
1421     // moves toward the stopping condition, so will always end.
1423     if($from == 0) {
1424         // We need a year smaller than $SESSION->dst_range[0]
1425         if($SESSION->dst_range[0] == 1971) {
1426             return 0;
1427         }
1428         calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL);
1429         return dst_offset_on($time);
1430     }
1431     else {
1432         // We need a year larger than $SESSION->dst_range[1]
1433         if($SESSION->dst_range[1] == 2035) {
1434             return 0;
1435         }
1436         calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5);
1437         return dst_offset_on($time);
1438     }
1441 function find_day_in_month($startday, $weekday, $month, $year) {
1443     $daysinmonth = days_in_month($month, $year);
1445     if($weekday == -1) {
1446         // Don't care about weekday, so return:
1447         //    abs($startday) if $startday != -1
1448         //    $daysinmonth otherwise
1449         return ($startday == -1) ? $daysinmonth : abs($startday);
1450     }
1452     // From now on we 're looking for a specific weekday
1454     // Give "end of month" its actual value, since we know it
1455     if($startday == -1) {
1456         $startday = -1 * $daysinmonth;
1457     }
1459     // Starting from day $startday, the sign is the direction
1461     if($startday < 1) {
1463         $startday = abs($startday);
1464         $lastmonthweekday  = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1466         // This is the last such weekday of the month
1467         $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1468         if($lastinmonth > $daysinmonth) {
1469             $lastinmonth -= 7;
1470         }
1472         // Find the first such weekday <= $startday
1473         while($lastinmonth > $startday) {
1474             $lastinmonth -= 7;
1475         }
1477         return $lastinmonth;
1479     }
1480     else {
1482         $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1484         $diff = $weekday - $indexweekday;
1485         if($diff < 0) {
1486             $diff += 7;
1487         }
1489         // This is the first such weekday of the month equal to or after $startday
1490         $firstfromindex = $startday + $diff;
1492         return $firstfromindex;
1494     }
1497 /**
1498  * Calculate the number of days in a given month
1499  *
1500  * @param int $month The month whose day count is sought
1501  * @param int $year The year of the month whose day count is sought
1502  * @return int
1503  */
1504 function days_in_month($month, $year) {
1505    return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1508 /**
1509  * Calculate the position in the week of a specific calendar day
1510  *
1511  * @param int $day The day of the date whose position in the week is sought
1512  * @param int $month The month of the date whose position in the week is sought
1513  * @param int $year The year of the date whose position in the week is sought
1514  * @return int
1515  */
1516 function dayofweek($day, $month, $year) {
1517     // I wonder if this is any different from
1518     // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1519     return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1522 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1524 /**
1525  * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1526  * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1527  * sesskey string if $USER exists, or boolean false if not.
1528  *
1529  * @uses $USER
1530  * @return string
1531  */
1532 function sesskey() {
1533     global $USER;
1535     if(!isset($USER)) {
1536         return false;
1537     }
1539     if (empty($USER->sesskey)) {
1540         $USER->sesskey = random_string(10);
1541     }
1543     return $USER->sesskey;
1547 /**
1548  * For security purposes, this function will check that the currently
1549  * given sesskey (passed as a parameter to the script or this function)
1550  * matches that of the current user.
1551  *
1552  * @param string $sesskey optionally provided sesskey
1553  * @return bool
1554  */
1555 function confirm_sesskey($sesskey=NULL) {
1556     global $USER;
1558     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1559         return true;
1560     }
1562     if (empty($sesskey)) {
1563         $sesskey = required_param('sesskey', PARAM_RAW);  // Check script parameters
1564     }
1566     if (!isset($USER->sesskey)) {
1567         return false;
1568     }
1570     return ($USER->sesskey === $sesskey);
1573 /**
1574  * Setup all global $CFG course variables, set locale and also themes
1575  * This function can be used on pages that do not require login instead of require_login()
1576  *
1577  * @param mixed $courseorid id of the course or course object
1578  */
1579 function course_setup($courseorid=0) {
1580     global $COURSE, $CFG, $SITE, $USER;
1582 /// Redefine global $COURSE if needed
1583     if (empty($courseorid)) {
1584         // no change in global $COURSE - for backwards compatibiltiy
1585         // if require_rogin() used after require_login($courseid); 
1586     } else if (is_object($courseorid)) {
1587         $COURSE = clone($courseorid);
1588     } else {
1589         global $course; // used here only to prevent repeated fetching from DB - may be removed later
1590         if (!empty($course->id) and $course->id == SITEID) {
1591             $COURSE = clone($SITE);
1592         } else if (!empty($course->id) and $course->id == $courseorid) {
1593             $COURSE = clone($course);
1594         } else {
1595             if (!$COURSE = get_record('course', 'id', $courseorid)) {
1596                 error('Invalid course ID');
1597             }
1598         }
1599     }
1601 /// set locale and themes
1602     moodle_setlocale();
1603     theme_setup();
1607 /**
1608  * This function checks that the current user is logged in and has the
1609  * required privileges
1610  *
1611  * This function checks that the current user is logged in, and optionally
1612  * whether they are allowed to be in a particular course and view a particular
1613  * course module.
1614  * If they are not logged in, then it redirects them to the site login unless
1615  * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1616  * case they are automatically logged in as guests.
1617  * If $courseid is given and the user is not enrolled in that course then the
1618  * user is redirected to the course enrolment page.
1619  * If $cm is given and the coursemodule is hidden and the user is not a teacher
1620  * in the course then the user is redirected to the course home page.
1621  *
1622  * @uses $CFG
1623  * @uses $SESSION
1624  * @uses $USER
1625  * @uses $FULLME
1626  * @uses SITEID
1627  * @uses $COURSE
1628  * @param mixed $courseorid id of the course or course object
1629  * @param bool $autologinguest
1630  * @param object $cm course module object
1631  */
1632 function require_login($courseorid=0, $autologinguest=true, $cm=null) {
1634     global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1636 /// setup global $COURSE, themes, language and locale
1637     course_setup($courseorid);
1639 /// If the user is not even logged in yet then make sure they are
1640     if (!isloggedin()) {
1641         //NOTE: $USER->site check was obsoleted by session test cookie,
1642         //      $USER->confirmed test is in login/index.php
1643         $SESSION->wantsurl = $FULLME;
1644         if (!empty($_SERVER['HTTP_REFERER'])) {
1645             $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
1646         }
1647         if ($autologinguest and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1648             $loginguest = '?loginguest=true';
1649         } else {
1650             $loginguest = '';
1651         }
1652         if (empty($CFG->loginhttps) or $autologinguest) { //do not require https for guest logins
1653             redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1654         } else {
1655             $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1656             redirect($wwwroot .'/login/index.php');
1657         }
1658         exit;
1659     }
1661 /// check whether the user should be changing password (but only if it is REALLY them)
1662     $userauth = get_auth_plugin($USER->auth);
1663     if (!empty($USER->preference['auth_forcepasswordchange']) && empty($USER->realuser)) {
1664         if ($userauth->can_change_password()) {
1665             $SESSION->wantsurl = $FULLME;
1666             if (method_exists($userauth, 'change_password_url') and $userauth->change_password_url()) {
1667                 //use plugin custom url
1668                 redirect($userauth->change_password_url());
1669             } else {
1670                 //use moodle internal method
1671                 if (empty($CFG->loginhttps)) {
1672                     redirect($CFG->wwwroot .'/login/change_password.php');
1673                 } else {
1674                     $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1675                     redirect($wwwroot .'/login/change_password.php');
1676                 }
1677             }
1678         } else {
1679             error(get_strin('nopasswordchangeforced', 'auth'));
1680         }
1681     }
1683 /// Check that the user account is properly set up
1684     if (user_not_fully_set_up($USER)) {
1685         $SESSION->wantsurl = $FULLME;
1686         redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1687     }
1689 /// Make sure current IP matches the one for this session (if required)
1690     if (!empty($CFG->tracksessionip)) {
1691         if ($USER->sessionIP != md5(getremoteaddr())) {
1692             error(get_string('sessionipnomatch', 'error'));
1693         }
1694     }
1696 /// Make sure the USER has a sesskey set up.  Used for checking script parameters.
1697     sesskey();
1699     // Check that the user has agreed to a site policy if there is one
1700     if (!empty($CFG->sitepolicy)) {
1701         if (!$USER->policyagreed) {
1702             $SESSION->wantsurl = $FULLME;
1703             redirect($CFG->wwwroot .'/user/policy.php');
1704         }
1705     }
1707 /// If the site is currently under maintenance, then print a message
1708     if (!has_capability('moodle/site:config',get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1709         if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1710             print_maintenance_message();
1711             exit;
1712         }
1713     }
1716     if ($COURSE->id == SITEID) {
1717 /// We can eliminate hidden site activities straight away
1718         if (!empty($cm) && !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', 
1719                                                       get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1720             redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
1721         }
1722         return;
1724     } else { 
1725 /// Check if the user can be in a particular course
1726         if (!$context = get_context_instance(CONTEXT_COURSE, $COURSE->id)) {
1727             print_error('nocontext');
1728         }
1730         if (empty($USER->switchrole[$context->id]) &&
1731             !($COURSE->visible && course_parent_visible($COURSE)) &&
1732                !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $COURSE->id)) ){
1733             print_header_simple();
1734             notice(get_string('coursehidden'), $CFG->wwwroot .'/');
1735         }    
1736         
1737     /// Non-guests who don't currently have access, check if they can be allowed in as a guest
1739         if ($USER->username != 'guest' and !has_capability('moodle/course:view', $context)) {
1740             if ($COURSE->guest == 1) {
1741                  // Temporarily assign them guest role for this context,
1742                  // if it fails user is asked to enrol
1743                  load_guest_role($context);
1744             }
1745         }
1747     /// If the user is a guest then treat them according to the course policy about guests
1749         if (has_capability('moodle/legacy:guest', $context, NULL, false)) {
1750             switch ($COURSE->guest) {    /// Check course policy about guest access
1752                 case 1:    /// Guests always allowed 
1753                     if (!has_capability('moodle/course:view', $context)) {    // Prohibited by capability
1754                         print_header_simple();
1755                         notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
1756                     }
1757                     if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
1758                         redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, 
1759                                  get_string('activityiscurrentlyhidden'));
1760                     }
1762                     return;   // User is allowed to see this course
1764                     break;
1766                 case 2:    /// Guests allowed with key 
1767                     if (!empty($USER->enrolkey[$COURSE->id])) {   // Set by enrol/manual/enrol.php
1768                         return true;
1769                     }
1770                     //  otherwise drop through to logic below (--> enrol.php)
1771                     break;
1773                 default:    /// Guests not allowed
1774                     print_header_simple('', '', get_string('loggedinasguest'));
1775                     if (empty($USER->switchrole[$context->id])) {  // Normal guest
1776                         notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
1777                     } else {
1778                         notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
1779                         echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
1780                         print_footer($COURSE);
1781                         exit;
1782                     }
1783                     break;
1784             }
1786     /// For non-guests, check if they have course view access
1788         } else if (has_capability('moodle/course:view', $context)) {
1789             if (!empty($USER->realuser)) {   // Make sure the REAL person can also access this course
1790                 if (!has_capability('moodle/course:view', $context, $USER->realuser)) {
1791                     print_header_simple();
1792                     notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
1793                 }
1794             }
1796         /// Make sure they can read this activity too, if specified
1798             if (!empty($cm) and !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) { 
1799                 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1800             }
1801             return;   // User is allowed to see this course
1803         }
1806     /// Currently not enrolled in the course, so see if they want to enrol
1807         $SESSION->wantsurl = $FULLME;
1808         redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
1809         die;
1810     }
1815 /**
1816  * This function just makes sure a user is logged out.
1817  *
1818  * @uses $CFG
1819  * @uses $USER
1820  */
1821 function require_logout() {
1823     global $USER, $CFG, $SESSION;
1825     if (isset($USER) and isset($USER->id)) {
1826         add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
1828         if ($USER->auth == 'cas' && !empty($CFG->cas_enabled)) {
1829             require($CFG->dirroot.'/auth/cas/logout.php');
1830         }
1831         
1832         if (extension_loaded('openssl')) {
1833             require($CFG->dirroot.'/auth/mnet/auth.php');
1834             $authplugin = new auth_plugin_mnet();
1835             $authplugin->logout();
1836         }
1837     }
1839     if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
1840         // This method is just to try to avoid silly warnings from PHP 4.3.0
1841         session_unregister("USER");
1842         session_unregister("SESSION");
1843     }
1845     setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath);
1846     unset($_SESSION['USER']);
1847     unset($_SESSION['SESSION']);
1849     unset($SESSION);
1850     unset($USER);
1854 /**
1855  * This is a weaker version of {@link require_login()} which only requires login
1856  * when called from within a course rather than the site page, unless
1857  * the forcelogin option is turned on.
1858  *
1859  * @uses $CFG
1860  * @param mixed $courseorid The course object or id in question
1861  * @param bool $autologinguest Allow autologin guests if that is wanted
1862  * @param object $cm Course activity module if known
1863  */
1864 function require_course_login($courseorid, $autologinguest=true, $cm=null) {
1865     global $CFG;
1866     if (!empty($CFG->forcelogin)) {
1867         // login required for both SITE and courses
1868         require_login($courseorid, $autologinguest, $cm);
1869     } else if ((is_object($courseorid) and $courseorid->id == SITEID)
1870           or (!is_object($courseorid) and $courseorid == SITEID)) {
1871         //login for SITE not required
1872     } else {
1873         // course login always required
1874         require_login($courseorid, $autologinguest, $cm);
1875     }
1878 /**
1879  * Modify the user table by setting the currently logged in user's
1880  * last login to now.
1881  *
1882  * @uses $USER
1883  * @return bool
1884  */
1885 function update_user_login_times() {
1886     global $USER;
1888     $user = new object();
1889     $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
1890     $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
1892     $user->id = $USER->id;
1894     return update_record('user', $user);
1897 /**
1898  * Determines if a user has completed setting up their account.
1899  *
1900  * @param user $user A {@link $USER} object to test for the existance of a valid name and email
1901  * @return bool
1902  */
1903 function user_not_fully_set_up($user) {
1904     return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
1907 function over_bounce_threshold($user) {
1909     global $CFG;
1911     if (empty($CFG->handlebounces)) {
1912         return false;
1913     }
1914     // set sensible defaults
1915     if (empty($CFG->minbounces)) {
1916         $CFG->minbounces = 10;
1917     }
1918     if (empty($CFG->bounceratio)) {
1919         $CFG->bounceratio = .20;
1920     }
1921     $bouncecount = 0;
1922     $sendcount = 0;
1923     if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1924         $bouncecount = $bounce->value;
1925     }
1926     if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1927         $sendcount = $send->value;
1928     }
1929     return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
1932 /**
1933  * @param $user - object containing an id
1934  * @param $reset - will reset the count to 0
1935  */
1936 function set_send_count($user,$reset=false) {
1937     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1938         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1939         update_record('user_preferences',$pref);
1940     }
1941     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1942         // make a new one
1943         $pref->name = 'email_send_count';
1944         $pref->value = 1;
1945         $pref->userid = $user->id;
1946         insert_record('user_preferences',$pref, false);
1947     }
1950 /**
1951 * @param $user - object containing an id
1952  * @param $reset - will reset the count to 0
1953  */
1954 function set_bounce_count($user,$reset=false) {
1955     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1956         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1957         update_record('user_preferences',$pref);
1958     }
1959     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1960         // make a new one
1961         $pref->name = 'email_bounce_count';
1962         $pref->value = 1;
1963         $pref->userid = $user->id;
1964         insert_record('user_preferences',$pref, false);
1965     }
1968 /**
1969  * Keeps track of login attempts
1970  *
1971  * @uses $SESSION
1972  */
1973 function update_login_count() {
1975     global $SESSION;
1977     $max_logins = 10;
1979     if (empty($SESSION->logincount)) {
1980         $SESSION->logincount = 1;
1981     } else {
1982         $SESSION->logincount++;
1983     }
1985     if ($SESSION->logincount > $max_logins) {
1986         unset($SESSION->wantsurl);
1987         error(get_string('errortoomanylogins'));
1988     }
1991 /**
1992  * Resets login attempts
1993  *
1994  * @uses $SESSION
1995  */
1996 function reset_login_count() {
1997     global $SESSION;
1999     $SESSION->logincount = 0;
2002 function sync_metacourses() {
2004     global $CFG;
2006     if (!$courses = get_records('course', 'metacourse', 1)) {
2007         return;
2008     }
2010     foreach ($courses as $course) {
2011         sync_metacourse($course);
2012     }
2015 /**
2016  * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2017  * 
2018  * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2019  */
2020 function sync_metacourse($course) {
2021     global $CFG;
2023     // Check the course is valid.
2024     if (!is_object($course)) {
2025         if (!$course = get_record('course', 'id', $course)) {
2026             return false; // invalid course id
2027         }
2028     }
2029     
2030     // Check that we actually have a metacourse.
2031     if (empty($course->metacourse)) {
2032         return false;
2033     }
2035     // Get a list of roles that should not be synced.
2036     if ($CFG->nonmetacoursesyncroleids) {
2037         $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2038     } else { 
2039         $roleexclusions = '';
2040     }
2042     // Get the context of the metacourse.
2043     $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2045     // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2046     if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2047         $managers = array_keys($users);
2048     } else {
2049         $managers = array();
2050     }
2052     // Get assignments of a user to a role that exist in a child course, but
2053     // not in the meta coure. That is, get a list of the assignments that need to be made.
2054     if (!$assignments = get_records_sql("
2055             SELECT
2056                 ra.id, ra.roleid, ra.userid
2057             FROM
2058                 {$CFG->prefix}role_assignments ra,
2059                 {$CFG->prefix}context con,
2060                 {$CFG->prefix}course_meta cm
2061             WHERE
2062                 ra.contextid = con.id AND
2063                 con.contextlevel = " . CONTEXT_COURSE . " AND
2064                 con.instanceid = cm.child_course AND
2065                 cm.parent_course = {$course->id} AND
2066                 $roleexclusions
2067                 NOT EXISTS (
2068                     SELECT 1 FROM
2069                         {$CFG->prefix}role_assignments ra2
2070                     WHERE
2071                         ra2.userid = ra.userid AND
2072                         ra2.roleid = ra.roleid AND
2073                         ra2.contextid = {$context->id}
2074                 )
2075     ")) {
2076         $assignments = array();
2077     }
2079     // Get assignments of a user to a role that exist in the meta course, but
2080     // not in any child courses. That is, get a list of the unassignments that need to be made.
2081     if (!$unassignments = get_records_sql("
2082             SELECT
2083                 ra.id, ra.roleid, ra.userid
2084             FROM
2085                 {$CFG->prefix}role_assignments ra
2086             WHERE
2087                 ra.contextid = {$context->id} AND
2088                 $roleexclusions
2089                 NOT EXISTS (
2090                     SELECT 1 FROM
2091                         {$CFG->prefix}role_assignments ra2,
2092                         {$CFG->prefix}context con2,
2093                         {$CFG->prefix}course_meta cm
2094                     WHERE
2095                         ra2.userid = ra.userid AND
2096                         ra2.roleid = ra.roleid AND
2097                         ra2.contextid = con2.id AND
2098                         con2.contextlevel = " . CONTEXT_COURSE . " AND
2099                         con2.instanceid = cm.child_course AND
2100                         cm.parent_course = {$course->id}
2101                 )
2102     ")) {
2103         $unassignments = array();
2104     }
2106     $success = true;
2108     // Make the unassignments, if they are not managers.
2109     foreach ($unassignments as $unassignment) {
2110         if (!in_array($unassignment->userid, $managers)) {
2111             $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2112         }
2113     }
2115     // Make the assignments.
2116     foreach ($assignments as $assignment) {
2117         $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id) && $success;
2118     }
2120     return $success;
2121     
2122 // TODO: finish timeend and timestart
2123 // maybe we could rely on cron job to do the cleaning from time to time
2126 /**
2127  * Adds a record to the metacourse table and calls sync_metacoures
2128  */
2129 function add_to_metacourse ($metacourseid, $courseid) {
2131     if (!$metacourse = get_record("course","id",$metacourseid)) {
2132         return false;
2133     }
2135     if (!$course = get_record("course","id",$courseid)) {
2136         return false;
2137     }
2139     if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2140         $rec = new object();
2141         $rec->parent_course = $metacourseid;
2142         $rec->child_course = $courseid;
2143         if (!insert_record('course_meta',$rec)) {
2144             return false;
2145         }
2146         return sync_metacourse($metacourseid);
2147     }
2148     return true;
2152 /**
2153  * Removes the record from the metacourse table and calls sync_metacourse
2154  */
2155 function remove_from_metacourse($metacourseid, $courseid) {
2157     if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2158         return sync_metacourse($metacourseid);
2159     }
2160     return false;
2164 /**
2165  * Determines if a user is currently logged in
2166  *
2167  * @uses $USER
2168  * @return bool
2169  */
2170 function isloggedin() {
2171     global $USER;
2173     return (!empty($USER->id));
2176 /**
2177  * Determines if a user is logged in as real guest user with username 'guest'.
2178  * This function is similar to original isguest() in 1.6 and earlier.
2179  * Current isguest() is deprecated - do not use it anymore.
2180  *
2181  * @param $user mixed user object or id, $USER if not specified
2182  * @return bool true if user is the real guest user, false if not logged in or other user
2183  */
2184 function isguestuser($user=NULL) {
2185     global $USER;
2186     if ($user === NULL) {
2187         $user = $USER;
2188     } else if (is_numeric($user)) {
2189         $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2190     }
2192     if (empty($user->id)) {
2193         return false; // not logged in, can not be guest
2194     }
2196     return ($user->username == 'guest');
2199 /**
2200  * Determines if the currently logged in user is in editing mode
2201  *
2202  * @uses $USER
2203  * @param int $courseid The id of the course being tested
2204  * @param user $user A {@link $USER} object. If null then the currently logged in user is used.
2205  * @return bool
2206  */
2207 function isediting($courseid, $user=NULL) {
2208     global $USER;
2209     if (!$user) {
2210         $user = $USER;
2211     }
2212     if (empty($user->editing)) {
2213         return false;
2214     }
2215     
2216     $capcheck = false;
2217     $coursecontext = get_context_instance(CONTEXT_COURSE, $courseid);
2218     
2219     if (has_capability('moodle/course:manageactivities', $coursecontext) ||
2220         has_capability('moodle/site:manageblocks', $coursecontext)) {
2221         $capcheck = true;      
2222     } else {
2223         // loop through all child context, see if user has moodle/course:manageactivities or moodle/site:manageblocks  
2224         if ($children = get_child_contexts($coursecontext)) {
2225             foreach ($children as $child) {
2226                 $childcontext = get_record('context', 'id', $child);
2227                 if (has_capability('moodle/course:manageactivities', $childcontext) ||
2228                     has_capability('moodle/site:manageblocks', $childcontext)) {
2229                     $capcheck = true;
2230                     break;
2231                 }             
2232             }          
2233         }
2234     }
2235       
2236     return ($user->editing && $capcheck);
2237     //return ($user->editing and has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $courseid)));
2240 /**
2241  * Determines if the logged in user is currently moving an activity
2242  *
2243  * @uses $USER
2244  * @param int $courseid The id of the course being tested
2245  * @return bool
2246  */
2247 function ismoving($courseid) {
2248     global $USER;
2250     if (!empty($USER->activitycopy)) {
2251         return ($USER->activitycopycourse == $courseid);
2252     }
2253     return false;
2256 /**
2257  * Given an object containing firstname and lastname
2258  * values, this function returns a string with the
2259  * full name of the person.
2260  * The result may depend on system settings
2261  * or language.  'override' will force both names
2262  * to be used even if system settings specify one.
2263  *
2264  * @uses $CFG
2265  * @uses $SESSION
2266  * @param object $user A {@link $USER} object to get full name of
2267  * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2268  */
2269 function fullname($user, $override=false) {
2271     global $CFG, $SESSION;
2273     if (!isset($user->firstname) and !isset($user->lastname)) {
2274         return '';
2275     }
2277     if (!$override) {
2278         if (!empty($CFG->forcefirstname)) {
2279             $user->firstname = $CFG->forcefirstname;
2280         }
2281         if (!empty($CFG->forcelastname)) {
2282             $user->lastname = $CFG->forcelastname;
2283         }
2284     }
2286     if (!empty($SESSION->fullnamedisplay)) {
2287         $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2288     }
2290     if ($CFG->fullnamedisplay == 'firstname lastname') {
2291         return $user->firstname .' '. $user->lastname;
2293     } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2294         return $user->lastname .' '. $user->firstname;
2296     } else if ($CFG->fullnamedisplay == 'firstname') {
2297         if ($override) {
2298             return get_string('fullnamedisplay', '', $user);
2299         } else {
2300             return $user->firstname;
2301         }
2302     }
2304     return get_string('fullnamedisplay', '', $user);
2307 /**
2308  * Sets a moodle cookie with an encrypted string
2309  *
2310  * @uses $CFG
2311  * @uses DAYSECS
2312  * @uses HOURSECS
2313  * @param string $thing The string to encrypt and place in a cookie
2314  */
2315 function set_moodle_cookie($thing) {
2316     global $CFG;
2318     if ($thing == 'guest') {  // Ignore guest account
2319         return;
2320     }
2322     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2324     $days = 60;
2325     $seconds = DAYSECS*$days;
2327     setCookie($cookiename, '', time() - HOURSECS, '/');
2328     setCookie($cookiename, rc4encrypt($thing), time()+$seconds, '/');
2331 /**
2332  * Gets a moodle cookie with an encrypted string
2333  *
2334  * @uses $CFG
2335  * @return string
2336  */
2337 function get_moodle_cookie() {
2338     global $CFG;
2340     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2342     if (empty($_COOKIE[$cookiename])) {
2343         return '';
2344     } else {
2345         $thing = rc4decrypt($_COOKIE[$cookiename]);
2346         return ($thing == 'guest') ? '': $thing;  // Ignore guest account
2347     }
2350 /**
2351  * Returns whether a given authentication plugin exists.
2352  *
2353  * @uses $CFG
2354  * @param string $auth Form of authentication to check for. Defaults to the
2355  *        global setting in {@link $CFG}.
2356  * @return boolean Whether the plugin is available.
2357  */
2358 function exists_auth_plugin($auth) {
2359     global $CFG;
2360     
2361     if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2362         return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2363     }
2364     return false;
2367 /**
2368  * Checks if a given plugin is in the list of enabled authentication plugins.
2369  * 
2370  * @param string $auth Authentication plugin.
2371  * @return boolean Whether the plugin is enabled.
2372  */
2373 function is_enabled_auth($auth) {
2374     global $CFG;
2376     if (empty($auth)) {
2377         return false;
2378     } else if ($auth == 'manual') {
2379         return true;
2380     }
2382     return in_array($auth, explode(',', $CFG->auth));
2385 /**
2386  * Returns an authentication plugin instance.
2387  *
2388  * @uses $CFG
2389  * @param string $auth name of authentication plugin
2390  * @return object An instance of the required authentication plugin.
2391  */
2392 function get_auth_plugin($auth) {
2393     global $CFG;
2394     
2395     // check the plugin exists first
2396     if (! exists_auth_plugin($auth)) {
2397         error("Authentication plugin '$auth' not found.");
2398     }
2399     
2400     // return auth plugin instance
2401     require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2402     $class = "auth_plugin_$auth";
2403     return new $class;
2406 /**
2407  * Returns true if an internal authentication method is being used.
2408  * if method not specified then, global default is assumed
2409  *
2410  * @uses $CFG
2411  * @param string $auth Form of authentication required
2412  * @return bool
2413  */
2414 function is_internal_auth($auth) {
2415     $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2416     return $authplugin->is_internal();
2419 /**
2420  * Returns an array of user fields
2421  *
2422  * @uses $CFG
2423  * @uses $db
2424  * @return array User field/column names
2425  */
2426 function get_user_fieldnames() {
2428     global $CFG, $db;
2430     $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2431     unset($fieldarray['ID']);
2433     return $fieldarray;
2436 /**
2437  * Creates a bare-bones user record
2438  *
2439  * @uses $CFG
2440  * @param string $username New user's username to add to record
2441  * @param string $password New user's password to add to record
2442  * @param string $auth Form of authentication required
2443  * @return object A {@link $USER} object
2444  * @todo Outline auth types and provide code example
2445  */
2446 function create_user_record($username, $password, $auth='') {
2447     global $CFG;
2449     //just in case check text case
2450     $username = trim(moodle_strtolower($username));
2452     $authplugin = get_auth_plugin($auth);
2454     if (method_exists($authplugin, 'get_userinfo')) {
2455         if ($newinfo = $authplugin->get_userinfo($username)) {
2456             $newinfo = truncate_userinfo($newinfo);
2457             foreach ($newinfo as $key => $value){
2458                 $newuser->$key = addslashes($value);
2459             }
2460         }
2461     }
2463     if (!empty($newuser->email)) {
2464         if (email_is_not_allowed($newuser->email)) {
2465             unset($newuser->email);
2466         }
2467     }
2469     $newuser->auth = (empty($auth)) ? 'manual' : $auth;
2470     $newuser->username = $username;
2471     
2472     // fix for MDL-8480
2473     // user CFG lang for user if $newuser->lang is empty
2474     // or $user->lang is not an installed language
2475     $sitelangs = array_keys(get_list_of_languages());
2476     if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
2477         $newuser -> lang = $CFG->lang;
2478     }    
2479     $newuser->confirmed = 1;
2480     $newuser->lastip = getremoteaddr();
2481     $newuser->timemodified = time();
2482     $newuser->mnethostid = $CFG->mnet_localhost_id;
2484     if (insert_record('user', $newuser)) {
2485         $user = get_complete_user_data('username', $newuser->username);
2486         if($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'}){
2487             set_user_preference('auth_forcepasswordchange', 1, $user->id);
2488         }
2489         update_internal_user_password($user, $password);
2490         return $user;
2491     }
2492     return false;
2495 /**
2496  * Will update a local user record from an external source
2497  *
2498  * @uses $CFG
2499  * @param string $username New user's username to add to record
2500  * @return user A {@link $USER} object
2501  */
2502 function update_user_record($username, $authplugin) {
2503     if (method_exists($authplugin, 'get_userinfo')) {
2504         $username = trim(moodle_strtolower($username)); /// just in case check text case
2506         $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2507         $userauth = get_auth_plugin($oldinfo->auth);
2509         if ($newinfo = $authplugin->get_userinfo($username)) {
2510             $newinfo = truncate_userinfo($newinfo);
2511             foreach ($newinfo as $key => $value){
2512                 $confkey = 'field_updatelocal_' . $key;
2513                 if (!empty($userauth->config->$confkey) and $userauth->config->$confkey === 'onlogin') {
2514                     $value = addslashes(stripslashes($value));   // Just in case
2515                     set_field('user', $key, $value, 'username', $username)
2516                         or error_log("Error updating $key for $username");
2517                 }
2518             }
2519         }
2520     }
2521     return get_complete_user_data('username', $username);
2524 function truncate_userinfo($info) {
2525 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2526 /// which may have large fields
2528     // define the limits
2529     $limit = array(
2530                     'username'    => 100,
2531                     'idnumber'    =>  64,
2532                     'firstname'   => 100,
2533                     'lastname'    => 100,
2534                     'email'       => 100,
2535                     'icq'         =>  15,
2536                     'phone1'      =>  20,
2537                     'phone2'      =>  20,
2538                     'institution' =>  40,
2539                     'department'  =>  30,
2540                     'address'     =>  70,
2541                     'city'        =>  20,
2542                     'country'     =>   2,
2543                     'url'         => 255,
2544                     );
2546     // apply where needed
2547     foreach (array_keys($info) as $key) {
2548         if (!empty($limit[$key])) {
2549             $info[$key] = trim(substr($info[$key],0, $limit[$key]));
2550         }
2551     }
2553     return $info;
2556 /**
2557  * Retrieve the guest user object
2558  *
2559  * @uses $CFG
2560  * @return user A {@link $USER} object
2561  */
2562 function guest_user() {
2563     global $CFG;
2565     if ($newuser = get_record('user', 'username', 'guest')) {
2566         $newuser->confirmed = 1;
2567         $newuser->lang = $CFG->lang;
2568         $newuser->lastip = getremoteaddr();
2569     }
2571     return $newuser;
2574 /**
2575  * Given a username and password, this function looks them
2576  * up using the currently selected authentication mechanism,
2577  * and if the authentication is successful, it returns a
2578  * valid $user object from the 'user' table.
2579  *
2580  * Uses auth_ functions from the currently active auth module
2581  *
2582  * @uses $CFG
2583  * @param string $username  User's username
2584  * @param string $password  User's password
2585  * @return user|flase A {@link $USER} object or false if error
2586  */
2587 function authenticate_user_login($username, $password) {
2589     global $CFG;
2591     if (empty($CFG->auth)) {
2592         $authsenabled = array('manual');
2593     } else {
2594         $authsenabled = explode(',', 'manual,'.$CFG->auth);
2595     }
2597     if ($user = get_complete_user_data('username', $username)) {
2598         $auth = empty($user->auth) ? 'manual' : $user->auth;  // use manual if auth not set
2599         if ($auth=='nologin' or !is_enabled_auth($auth)) {
2600             add_to_log(0, 'login', 'error', 'index.php', $username);
2601             error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Disabled Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
2602             return false;
2603         }
2604         if (!empty($user->deleted)) {
2605             add_to_log(0, 'login', 'error', 'index.php', $username);
2606             error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Deleted Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
2607             return false;
2608         }
2609         $auths = array($auth);
2611     } else {
2612         $auths = $authsenabled;
2613         $user = new object();
2614         $user->id = 0;     // User does not exist
2615     }
2617     foreach ($auths as $auth) {
2618         $authplugin = get_auth_plugin($auth);
2620         // on auth fail fall through to the next plugin
2621         if (!$authplugin->user_login($username, $password)) {
2622             continue;
2623         }
2625         // successful authentication
2626         if ($user->id) {                          // User already exists in database
2627             if (empty($user->auth)) {             // For some reason auth isn't set yet
2628                 set_field('user', 'auth', $auth, 'username', $username);
2629                 $user->auth = $auth;
2630             }
2632             update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
2634             if (!$authplugin->is_internal()) {            // update user record from external DB
2635                 $user = update_user_record($username, get_auth_plugin($user->auth));
2636             }
2637         } else {
2638             // if user not found, create him
2639             $user = create_user_record($username, $password, $auth);
2640         }
2641         // fix for MDL-6928
2642         if (method_exists($authplugin, 'iscreator')) {
2643             $sitecontext = get_context_instance(CONTEXT_SYSTEM);
2644             if ($creatorroles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
2645                 $creatorrole = array_shift($creatorroles); // We can only use one, let's use the first one
2646                 // Check if the user is a creator
2647                 if ($authplugin->iscreator($username)) { // Following calls will not create duplicates
2648                     role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, $auth);
2649                 } else {
2650                     role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id);
2651                 }
2652             }
2653         }
2655     /// Log in to a second system if necessary
2656         if (!empty($CFG->sso)) {
2657             include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
2658             if (function_exists('sso_user_login')) {
2659                 if (!sso_user_login($username, $password)) {   // Perform the signon process
2660                     notify('Second sign-on failed');
2661                 }
2662             }
2663         }
2665         return $user;
2667     } 
2668     
2669     // failed if all the plugins have failed
2670     add_to_log(0, 'login', 'error', 'index.php', $username);
2671     error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Failed Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
2672     return false;
2675 /**
2676  * Compare password against hash stored in internal user table.
2677  * If necessary it also updates the stored hash to new format.
2678  * 
2679  * @param object user
2680  * @param string plain text password
2681  * @return bool is password valid?
2682  */
2683 function validate_internal_user_password(&$user, $password) {
2684     global $CFG;
2686     if (!isset($CFG->passwordsaltmain)) {
2687         $CFG->passwordsaltmain = '';
2688     }
2690     $validated = false;
2692         // get password original encoding in case it was not updated to unicode yet
2693     $textlib = textlib_get_instance();
2694     $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
2696     if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
2697         or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
2698         $validated = true;
2699     } else {
2700         for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
2701             $alt = 'passwordsaltalt'.$i;
2702             if (!empty($CFG->$alt)) {
2703                 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
2704                     $validated = true;
2705                     break;
2706                 }
2707             }
2708         }
2709     }
2711     if ($validated) {
2712         // force update of password hash using latest main password salt and encoding if needed
2713         update_internal_user_password($user, $password);
2714     }
2716     return $validated;
2719 /**
2720  * Calculate hashed value from password using current hash mechanism.
2721  * 
2722  * @param string password
2723  * @return string password hash
2724  */
2725 function hash_internal_user_password($password) {
2726     global $CFG;
2728     if (isset($CFG->passwordsaltmain)) {
2729         return md5($password.$CFG->passwordsaltmain);
2730     } else {
2731         return md5($password);
2732     }
2735 /**
2736  * Update pssword hash in user object.
2737  * 
2738  * @param object user
2739  * @param string plain text password
2740  * @param bool store changes also in db, default true
2741  * @return true if hash changed
2742  */
2743 function update_internal_user_password(&$user, $password) {
2744     global $CFG;
2746     $authplugin = get_auth_plugin($user->auth);
2747     if (!empty($authplugin->config->preventpassindb)) {
2748         $hashedpassword = 'not cached';
2749     } else {
2750         $hashedpassword = hash_internal_user_password($password);
2751     }
2753     return set_field('user', 'password',  $hashedpassword, 'id', $user->id);
2756 /**
2757  * Get a complete user record, which includes all the info
2758  * in the user record
2759  * Intended for setting as $USER session variable
2760  *
2761  * @uses $CFG
2762  * @uses SITEID
2763  * @param string $field The user field to be checked for a given value.
2764  * @param string $value The value to match for $field.
2765  * @return user A {@link $USER} object.
2766  */
2767 function get_complete_user_data($field, $value, $mnethostid=null) {
2769     global $CFG;
2771     if (!$field || !$value) {
2772         return false;
2773     }
2775 /// Build the WHERE clause for an SQL query
2777     $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
2779     if (null === $mnethostid) {
2780         $constraints .= ' AND auth != \'mnet\'';
2781     } elseif (is_numeric($mnethostid)) {
2782         $constraints .= ' AND mnethostid = \''.$mnethostid.'\'';
2783     } else {
2784         error_log('Call to get_complete_user_data for $field='.$field.', $value = '.$value.', with invalid $mnethostid: '. $mnethostid);
2785         print_error('invalidhostlogin','mnet', $CFG->wwwroot.'/login/index.php');
2786         exit;
2787     }
2789 /// Get all the basic user data
2791     if (! $user = get_record_select('user', $constraints)) {
2792         return false;
2793     }
2795 /// Get various settings and preferences
2797     if ($displays = get_records('course_display', 'userid', $user->id)) {
2798         foreach ($displays as $display) {
2799             $user->display[$display->course] = $display->display;
2800         }
2801     }
2803     if ($preferences = get_records('user_preferences', 'userid', $user->id)) {
2804         foreach ($preferences as $preference) {
2805             $user->preference[$preference->name] = $preference->value;
2806         }
2807     }
2809     if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
2810         foreach ($lastaccesses as $lastaccess) {
2811             $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
2812         }
2813     }
2815     if ($groupids = groups_get_all_groups_for_user($user->id)) { //TODO:check.
2816         foreach ($groupids as $groupid) {
2817             $courseid = groups_get_course($groupid);
2818             //change this to 2D array so we can put multiple groups in a course
2819             $user->groupmember[$courseid][] = $groupid;
2820         }
2821     }
2823 /// Rewrite some variables if necessary
2824     if (!empty($user->description)) {
2825         $user->description = true;   // No need to cart all of it around
2826     }
2827     if ($user->username == 'guest') {
2828         $user->lang       = $CFG->lang;               // Guest language always same as site
2829         $user->firstname  = get_string('guestuser');  // Name always in current language
2830         $user->lastname   = ' ';
2831     }
2833     $user->sesskey  = random_string(10);
2834     $user->sessionIP = md5(getremoteaddr());   // Store the current IP in the session
2836     return $user;
2841 /*
2842  * When logging in, this function is run to set certain preferences
2843  * for the current SESSION
2844  */
2845 function set_login_session_preferences() {
2846     global $SESSION, $CFG;
2848     $SESSION->justloggedin = true;
2850     unset($SESSION->lang);
2852     // Restore the calendar filters, if saved
2853     if (intval(get_user_preferences('calendar_persistflt', 0))) {
2854         include_once($CFG->dirroot.'/calendar/lib.php');
2855         calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
2856     }
2860 /**
2861  * Delete a course, including all related data from the database,
2862  * and any associated files from the moodledata folder.
2863  *
2864  * @param int $courseid The id of the course to delete.
2865  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2866  * @return bool true if all the removals succeeded. false if there were any failures. If this
2867  *             method returns false, some of the removals will probably have succeeded, and others
2868  *             failed, but you have no way of knowing which.
2869  */
2870 function delete_course($courseid, $showfeedback = true) {
2871     global $CFG;
2872     $result = true;
2874     if (!remove_course_contents($courseid, $showfeedback)) {
2875         if ($showfeedback) {
2876             notify("An error occurred while deleting some of the course contents.");
2877         }
2878         $result = false;
2879     }
2881     if (!delete_records("course", "id", $courseid)) {
2882         if ($showfeedback) {
2883             notify("An error occurred while deleting the main course record.");
2884         }
2885         $result = false;
2886     }
2888     if (!delete_records('context', 'contextlevel', CONTEXT_COURSE, 'instanceid', $courseid)) {
2889         if ($showfeedback) {
2890             notify("An error occurred while deleting the main context record.");
2891         }
2892         $result = false;
2893     }
2895     if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
2896         if ($showfeedback) {
2897             notify("An error occurred while deleting the course files.");
2898         }
2899         $result = false;
2900     }
2902     return $result;
2905 /**
2906  * Clear a course out completely, deleting all content
2907  * but don't delete the course itself
2908  *
2909  * @uses $CFG
2910  * @param int $courseid The id of the course that is being deleted
2911  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2912  * @return bool true if all the removals succeeded. false if there were any failures. If this
2913  *             method returns false, some of the removals will probably have succeeded, and others
2914  *             failed, but you have no way of knowing which.
2915  */
2916 function remove_course_contents($courseid, $showfeedback=true) {
2918     global $CFG;
2920     $result = true;
2922     if (! $course = get_record('course', 'id', $courseid)) {
2923         error('Course ID was incorrect (can\'t find it)');
2924     }
2926     $strdeleted = get_string('deleted');
2928 /// First delete every instance of every module
2930     if ($allmods = get_records('modules') ) {
2931         foreach ($allmods as $mod) {
2932             $modname = $mod->name;
2933             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
2934             $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance
2935             $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon)
2936             $count=0;
2937             if (file_exists($modfile)) {
2938                 include_once($modfile);
2939                 if (function_exists($moddelete)) {
2940                     if ($instances = get_records($modname, 'course', $course->id)) {
2941                         foreach ($instances as $instance) {
2942                             if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
2943                                 delete_context(CONTEXT_MODULE, $cm->id);
2944                             }
2945                             if ($moddelete($instance->id)) {
2946                                 $count++;
2948                             } else {
2949                                 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
2950                                 $result = false;
2951                             }
2952                         }
2953                     }
2954                 } else {
2955                     notify('Function '. $moddelete() .'doesn\'t exist!');
2956                     $result = false;
2957                 }
2959                 if (function_exists($moddeletecourse)) {
2960                     $moddeletecourse($course, $showfeedback);
2961                 }
2962             }
2963             if ($showfeedback) {
2964                 notify($strdeleted .' '. $count .' x '. $modname);
2965             }
2966         }
2967     } else {
2968         error('No modules are installed!');
2969     }
2971 /// Give local code a chance to delete its references to this course.
2972     require_once('locallib.php');
2973     notify_local_delete_course($courseid, $showfeedback);
2975 /// Delete course blocks
2977     if ($blocks = get_records_sql("SELECT * 
2978                                    FROM {$CFG->prefix}block_instance
2979                                    WHERE pagetype = '".PAGE_COURSE_VIEW."'
2980                                    AND pageid = $course->id")) {
2981         if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
2982             if ($showfeedback) {
2983                 notify($strdeleted .' block_instance');
2984             }
2985             foreach ($blocks as $block) {  /// Delete any associated contexts for this block
2986                 delete_context(CONTEXT_BLOCK, $block->id);
2987             }
2988         } else {
2989             $result = false;
2990         }
2991     }
2993 /// Delete any groups, removing members and grouping/course links first.
2994     //TODO: If groups or groupings are to be shared between courses, think again!
2995     if ($groupids = groups_get_groups($course->id)) {
2996         foreach ($groupids as $groupid) {
2997             if (groups_remove_all_members($groupid)) {
2998                 if ($showfeedback) {
2999                     notify($strdeleted .' groups_members');
3000                 }
3001             } else {
3002                 $result = false;
3003             }
3004             /// Delete any associated context for this group ??
3005             delete_context(CONTEXT_GROUP, $groupid);
3006             
3007             if (groups_delete_group($groupid)) {
3008                 if ($showfeedback) {
3009                     notify($strdeleted .' groups');
3010                 }
3011             } else {
3012                 $result = false;
3013             }
3014         }
3015     }
3016 /// Delete any groupings.
3017     $result = groups_delete_all_groupings($course->id);
3018     if ($result && $showfeedback) {
3019         notify($strdeleted .' groupings');
3020     }
3022 /// Delete all related records in other tables that may have a courseid
3023 /// This array stores the tables that need to be cleared, as
3024 /// table_name => column_name that contains the course id.
3026     $tablestoclear = array(
3027         'event' => 'courseid', // Delete events
3028         'log' => 'course', // Delete logs
3029         'course_sections' => 'course', // Delete any course stuff
3030         'course_modules' => 'course',
3031         'grade_category' => 'courseid', // Delete gradebook stuff
3032         'grade_exceptions' => 'courseid',
3033         'grade_item' => 'courseid',
3034         'grade_letter' => 'courseid',
3035         'grade_preferences' => 'courseid',
3036         'backup_courses' => 'courseid', // Delete scheduled backup stuff
3037         'backup_log' => 'courseid'
3038     );
3039     foreach ($tablestoclear as $table => $col) {
3040         if (delete_records($table, $col, $course->id)) {
3041             if ($showfeedback) {
3042                 notify($strdeleted . ' ' . $table);
3043             }
3044         } else {
3045             $result = false;
3046         }
3047     }
3050 /// Clean up metacourse stuff
3052     if ($course->metacourse) {
3053         delete_records("course_meta","parent_course",$course->id);
3054         sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3055         if ($showfeedback) {
3056             notify("$strdeleted course_meta");
3057         }
3058     } else {
3059         if ($parents = get_records("course_meta","child_course",$course->id)) {
3060             foreach ($parents as $parent) {
3061                 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3062             }
3063             if ($showfeedback) {
3064                 notify("$strdeleted course_meta");
3065             }
3066         }
3067     }
3069 /// Delete questions and question categories
3070     include_once($CFG->libdir.'/questionlib.php');
3071     question_delete_course($course, $showfeedback);
3073 /// Delete all roles and overiddes in the course context (but keep the course context)
3074     if ($courseid != SITEID) {
3075         delete_context(CONTEXT_COURSE, $course->id);
3076     }
3077     
3078     return $result;
3082 /**
3083  * This function will empty a course of USER data as much as
3084 /// possible. It will retain the activities and the structure
3085 /// of the course.
3086  *
3087  * @uses $USER
3088  * @uses $SESSION
3089  * @uses $CFG
3090  * @param object $data an object containing all the boolean settings and courseid
3091  * @param bool $showfeedback  if false then do it all silently
3092  * @return bool
3093  * @todo Finish documenting this function
3094  */
3095 function reset_course_userdata($data, $showfeedback=true) {
3097     global $CFG, $USER, $SESSION;
3099     $result = true;
3101     $strdeleted = get_string('deleted');
3103     // Look in every instance of every module for data to delete
3105     if ($allmods = get_records('modules') ) {
3106         foreach ($allmods as $mod) {
3107             $modname = $mod->name;
3108             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3109             $moddeleteuserdata = $modname .'_delete_userdata';   // Function to delete user data
3110             if (file_exists($modfile)) {
3111                 @include_once($modfile);
3112                 if (function_exists($moddeleteuserdata)) {
3113                     $moddeleteuserdata($data, $showfeedback);
3114                 }
3115             }
3116         }
3117     } else {
3118         error('No modules are installed!');
3119     }
3121     // Delete other stuff
3122     $coursecontext = get_context_instance(CONTEXT_COURSE, $data->courseid);
3124     if (!empty($data->reset_students) or !empty($data->reset_teachers)) {
3125         $teachers     = array_keys(get_users_by_capability($coursecontext, 'moodle/course:update'));
3126         $participants = array_keys(get_users_by_capability($coursecontext, 'moodle/course:view'));
3127         $students     = array_diff($participants, $teachers);
3129         if (!empty($data->reset_students)) {
3130             foreach ($students as $studentid) {
3131                 role_unassign(0, $studentid, 0, $coursecontext->id);
3132             }
3133             if ($showfeedback) {
3134                 notify($strdeleted .' '.get_string('students'), 'notifysuccess');
3135             }
3137             /// Delete group members (but keep the groups) TODO:check.
3138             if ($groupids = groups_get_groups($data->courseid)) {
3139                 foreach ($groupids as $groupid) {
3140                     if (groups_remove_all_group_members($groupid)) {
3141                         if ($showfeedback) {
3142                             notify($strdeleted .' groups_members', 'notifysuccess');
3143                         }
3144                     } else {
3145                         $result = false;
3146                     }
3147                 }
3148             }
3149         }
3151         if (!empty($data->reset_teachers)) {
3152             foreach ($teachers as $teacherid) {
3153                 role_unassign(0, $teacherid, 0, $coursecontext->id);
3154             }
3155             if ($showfeedback) {
3156                 notify($strdeleted .' '.get_string('teachers'), 'notifysuccess');
3157             }
3158         }
3159     }
3161     if (!empty($data->reset_groups)) {
3162         if ($groupids = groups_get_groups($data->courseid)) {
3163             foreach ($groupids as $groupid) {
3164                 if (groups_delete_group($groupid)) {
3165                     if ($showfeedback) {
3166                         notify($strdeleted .' groups', 'notifysuccess');
3167                     }
3168                 } else {
3169                     $result = false;
3170                 }
3171             }
3172         }
3173     }
3175     if (!empty($data->reset_events)) {
3176         if (delete_records('event', 'courseid', $data->courseid)) {
3177             if ($showfeedback) {
3178                 notify($strdeleted .' event', 'notifysuccess');
3179             }
3180         } else {
3181             $result = false;
3182         }
3183     }
3185     if (!empty($data->reset_logs)) {
3186         if (delete_records('log', 'course', $data->courseid)) {
3187             if ($showfeedback) {
3188                 notify($strdeleted .' log', 'notifysuccess');
3189             }
3190         } else {
3191             $result = false;
3192         }
3193     }
3195     // deletes all role assignments, and local override, these have no courseid in table and needs separate process
3196     $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3197     delete_records('role_capabilities', 'contextid', $context->id);
3199     return $result;
3203 require_once($CFG->dirroot.'/group/lib.php');
3204 /*TODO: functions moved to /group/lib/legacylib.php
3206 ismember
3207 add_user_to_group
3208 mygroupid
3209 groupmode
3210 set_current_group
3211 ... */
3214 function generate_email_processing_address($modid,$modargs) {
3215     global $CFG;
3217     if (empty($CFG->siteidentifier)) {    // Unique site identification code
3218         set_config('siteidentifier', random_string(32));
3219     }
3221     $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3222     return $header . substr(md5($header.$CFG->siteidentifier),0,16).'@'.$CFG->maildomain;
3226 function moodle_process_email($modargs,$body) {
3227     // the first char should be an unencoded letter. We'll take this as an action
3228     switch ($modargs{0}) {
3229         case 'B': { // bounce
3230             list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3231             if ($user = get_record_select("user","id=$userid","id,email")) {
3232                 // check the half md5 of their email
3233                 $md5check = substr(md5($user->email),0,16);
3234                 if ($md5check == substr($modargs, -16)) {
3235                     set_bounce_count($user);
3236                 }
3237                 // else maybe they've already changed it?
3238             }
3239         }
3240         break;
3241         // maybe more later?
3242     }
3245 /// CORRESPONDENCE  ////////////////////////////////////////////////
3247 /**
3248  * Send an email to a specified user
3249  *
3250  * @uses $CFG
3251  * @uses $FULLME
3252  * @uses SITEID
3253  * @param user $user  A {@link $USER} object
3254  * @param user $from A {@link $USER} object
3255  * @param string $subject plain text subject line of the email
3256  * @param string $messagetext plain text version of the message
3257  * @param string $messagehtml complete html version of the message (optional)
3258  * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
3259  * @param string $attachname the name of the file (extension indicates MIME)
3260  * @param bool $usetrueaddress determines whether $from email address should
3261  *          be sent out. Will be overruled by user profile setting for maildisplay
3262  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3263  *          was blocked by user and "false" if there was another sort of error.
3264  */
3265 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='') {
3267     global $CFG, $FULLME;
3269     include_once($CFG->libdir .'/phpmailer/class.phpmailer.php');
3271 /// We are going to use textlib services here
3272     $textlib = textlib_get_instance();
3274     if (empty($user)) {
3275         return false;
3276     }
3278     // skip mail to suspended users
3279     if ($user->auth=='nologin') {
3280         return true;
3281     }
3283     if (!empty($user->emailstop)) {
3284         return 'emailstop';
3285     }
3287     if (over_bounce_threshold($user)) {
3288         error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
3289         return false;
3290     }
3292     $mail = new phpmailer;
3294     $mail->Version = 'Moodle '. $CFG->version;           // mailer version
3295     $mail->PluginDir = $CFG->libdir .'/phpmailer/';      // plugin directory (eg smtp plugin)
3297     $mail->CharSet = 'utf-8';
3299     if ($CFG->smtphosts == 'qmail') {
3300         $mail->IsQmail();                              // use Qmail system
3302     } else if (empty($CFG->smtphosts)) {
3303         $mail->IsMail();                               // use PHP mail() = sendmail
3305     } else {
3306         $mail->IsSMTP();                               // use SMTP directly
3307         if (debugging()) {
3308             echo '<pre>' . "\n";
3309             $mail->SMTPDebug = true;
3310         }
3311         $mail->Host = $CFG->smtphosts;               // specify main and backup servers
3313         if ($CFG->smtpuser) {                          // Use SMTP authentication
3314             $mail->SMTPAuth = true;
3315             $mail->Username = $CFG->smtpuser;
3316             $mail->Password = $CFG->smtppass;
3317         }
3318     }
3320     $adminuser = get_admin();
3322     // make up an email address for handling bounces
3323     if (!empty($CFG->handlebounces)) {
3324         $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
3325         $mail->Sender = generate_email_processing_address(0,$modargs);
3326     }
3327     else {
3328         $mail->Sender   = $adminuser->email;
3329     }
3331     if (is_string($from)) { // So we can pass whatever we want if there is need
3332         $mail->From     = $CFG->noreplyaddress;
3333         $mail->FromName = $from;
3334     } else if ($usetrueaddress and $from->maildisplay) {
3335         $mail->From     = $from->email;
3336         $mail->FromName = fullname($from);
3337     } else {
3338         $mail->From     = $CFG->noreplyaddress;
3339         $mail->FromName = fullname($from);
3340         if (empty($replyto)) {
3341             $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
3342         }
3343     }
3345     if (!empty($replyto)) {
3346         $mail->AddReplyTo($replyto,$replytoname);
3347     }
3349     $mail->Subject = substr(stripslashes($subject), 0, 900);
3351     $mail->AddAddress($user->email, fullname($user) );
3353     $mail->WordWrap = 79;                               // set word wrap
3355     if (!empty($from->customheaders)) {                 // Add custom headers
3356         if (is_array($from->customheaders)) {
3357             foreach ($from->customheaders as $customheader) {
3358                 $mail->AddCustomHeader($customheader);
3359             }
3360         } else {
3361             $mail->AddCustomHeader($from->customheaders);
3362         }
3363     }
3365     if (!empty($from->priority)) {
3366         $mail->Priority = $from->priority;
3367     }
3369     if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
3370         $mail->IsHTML(true);
3371         $mail->Encoding = 'quoted-printable';           // Encoding to use
3372         $mail->Body    =  $messagehtml;
3373         $mail->AltBody =  "\n$messagetext\n";
3374     } else {
3375         $mail->IsHTML(false);
3376         $mail->Body =  "\n$messagetext\n";
3377     }
3379     if ($attachment && $attachname) {
3380         if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
3381             $mail->AddAddress($adminuser->email, fullname($adminuser) );
3382             $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
3383         } else {
3384             require_once($CFG->libdir.'/filelib.php');
3385             $mimetype = mimeinfo('type', $attachname);
3386             $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
3387         }
3388     }
3392 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
3393 /// encoding to the specified one
3394     if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
3395     /// Set it to site mail charset
3396         $charset = $CFG->sitemailcharset;
3397     /// Overwrite it with the user mail charset
3398         if (!empty($CFG->allowusermailcharset)) {
3399             if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
3400                 $charset = $useremailcharset;
3401             }
3402         }
3403     /// If it has changed, convert all the necessary strings
3404         if ($mail->CharSet != $charset) {
3405         /// Save the new mail charset
3406             $mail->CharSet = $charset;
3407         /// And convert some strings
3408             $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
3409             foreach ($mail->ReplyTo as $key => $rt) {                                      //ReplyTo Names
3410                 $mail->ReplyTo[$key][1] = $textlib->convert($rt, 'utf-8', $mail->CharSet);
3411             }
3412             $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet);   //Subject
3413             foreach ($mail->to as $key => $to) {
3414                 $mail->to[$key][1] = $textlib->convert($to, 'utf-8', $mail->CharSet);      //To Names
3415             }
3416             $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet);         //Body
3417             $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet);   //Subject
3418         }
3419     }
3421     if ($mail->Send()) {
3422         set_send_count($user);
3423         return true;
3424     } else {
3425         mtrace('ERROR: '. $mail->ErrorInfo);
3426         add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
3427         return false;
3428     }
3431 /**
3432  * Sets specified user's password and send the new password to the user via email.
3433  *
3434  * @uses $CFG
3435  * @param user $user A {@link $USER} object
3436  * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
3437  *          was blocked by user and "false" if there was another sort of error.
3438  */
3439 function setnew_password_and_mail($user) {
3441     global $CFG;
3443     $site  = get_site();
3444     $from = get_admin();
3446     $newpassword = generate_password();
3448     if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
3449         trigger_error('Could not set user password!');
3450         return false;
3451     }
3453     $a = new object();
3454     $a->firstname   = $user->firstname;
3455     $a->sitename    = $site->fullname;
3456     $a->username    = $user->username;
3457     $a->newpassword = $newpassword;
3458     $a->link        = $CFG->wwwroot .'/login/';
3459     $a->signoff     = fullname($from, true).' ('. $from->email .')';
3461     $message = get_string('newusernewpasswordtext', '', $a);
3463     $subject  = $site->fullname .': '. get_string('newusernewpasswordsubj');
3465     return email_to_user($user, $from, $subject, $message);
3469 /**
3470  * Resets specified user's password and send the new password to the user via email.
3471  *
3472  * @uses $CFG
3473  * @param user $user A {@link $USER} object
3474  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3475  *          was blocked by user and "false" if there was another sort of error.
3476  */
3477 function reset_password_and_mail($user) {
3479     global $CFG;
3481     $site  = get_site();
3482     $from = get_admin();
3484     $userauth = get_auth_plugin($user->auth);
3485     if (!$userauth->can_reset_password()) {
3486         trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
3487         return false;
3488     }
3490     $newpassword = generate_password();
3492     if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
3493         error("Could not set user password!");
3494     }
3496     $a = new object();
3497     $a->firstname = $user->firstname;
3498     $a->sitename = $site->fullname;
3499     $a->username = $user->username;
3500     $a->newpassword = $newpassword;
3501     $a->link = $CFG->httpswwwroot .'/login/change_password.php';
3502     $a->signoff = fullname($from, true).' ('. $from->email .')';
3504     $message = get_string('newpasswordtext', '', $a);
3506     $subject  = $site->fullname .': '. get_string('changedpassword');
3508     return email_to_user($user, $from, $subject, $message);
3512 /**
3513  * Send email to specified user with confirmation text and activation link.
3514  *
3515  * @uses $CFG
3516  * @param user $user A {@link $USER} object
3517  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3518  *          was blocked by user and "false" if there was another sort of error.
3519  */
3520  function send_confirmation_email($user) {
3522     global $CFG;
3524     $site = get_site();
3525     $from = get_admin();
3527     $data = new object();
3528     $data->firstname = fullname($user);
3529     $data->sitename = $site->fullname;
3530     $data->admin = fullname($from) .' ('. $from->email .')';
3532     $subject = get_string('emailconfirmationsubject', '', $site->fullname);
3534     $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $user->username;
3535     $message     = get_string('emailconfirmation', '', $data);
3536     $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
3538     $user->mailformat = 1;  // Always send HTML version as well
3540     return email_to_user($user, $from, $subject, $message, $messagehtml);
3544 /**
3545  * send_password_change_confirmation_email.
3546  *
3547  * @uses $CFG
3548  * @param user $user A {@link $USER} object
3549  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3550  *          was blocked by user and "false" if there was another sort of error.
3551  */
3552 function send_password_change_confirmation_email($user) {
3554     global $CFG;
3556     $site = get_site();
3557     $from = get_admin();
3559     $data = new object();
3560     $data->firstname = $user->firstname;
3561     $data->sitename = $site->fullname;
3562     $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. $user->username;
3563     $data->admin = fullname($from).' ('. $from->email .')';
3565     $message = get_string('emailpasswordconfirmation', '', $data);
3566     $subject = get_string('emailpasswordconfirmationsubject', '', $site->fullname);
3568     return email_to_user($user, $from, $subject, $message);
3572 /**
3573  * send_password_change_info.
3574  *
3575  * @uses $CFG
3576  * @param user $user A {@link $USER} object
3577  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3578  *          was blocked by user and "false" if there was another sort of error.
3579  */
3580 function send_password_change_info($user) {
3582     global $CFG;
3584     $site = get_site();
3585     $from = get_admin();
3587     $data = new object();
3588     $data->firstname = $user->firstname;
3589     $data->sitename = $site->fullname;
3590     $data->admin = fullname($from).' ('. $from->email .')';
3592      $userauth = get_auth_plugin($user->auth);
3593     if ($userauth->can_change_password() and method_exists($userauth, 'change_password_url') and $userauth->change_password_url()) {
3594         // we have some external url for password cahnging
3595         $data->link .= $userauth->change_password_url();
3597     } else {
3598         //no way to change password, sorry
3599         $data->link = '';
3600     }
3602     if (!empty($data->link)) {
3603         $message = get_string('emailpasswordchangeinfo', '', $data);
3604         $subject = get_string('emailpasswordchangeinfosubject', '', $site->fullname);
3605     } else {
3606         $message = get_string('emailpasswordchangeinfofail', '', $data);
3607         $subject = get_string('emailpasswordchangeinfosubject', '', $site->fullname);
3608     }
3610     return email_to_user($user, $from, $subject, $message);
3614 /**
3615  * Check that an email is allowed.  It returns an error message if there
3616  * was a problem.
3617  *
3618  * @uses $CFG
3619  * @param  string $email Content of email
3620  * @return string|false
3621  */
3622 function email_is_not_allowed($email) {
3624     global $CFG;
3626     if (!empty($CFG->allowemailaddresses)) {
3627         $allowed = explode(' ', $CFG->allowemailaddresses);
3628         foreach ($allowed as $allowedpattern) {
3629             $allowedpattern = trim($allowedpattern);
3630             if (!$allowedpattern) {
3631                 continue;
3632             }
3633             if (strpos(strrev($email), strrev($allowedpattern)) === 0) { // Match!   (bug 5250)
3634                 return false;
3635             }
3636         }
3637         return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
3639     } else if (!empty($CFG->denyemailaddresses)) {
3640         $denied = explode(' ', $CFG->denyemailaddresses);
3641         foreach ($denied as $deniedpattern) {
3642             $deniedpattern = trim($deniedpattern);
3643             if (!$deniedpattern) {
3644                 continue;
3645             }
3646             if (strpos(strrev($email), strrev($deniedpattern)) === 0) { // Match!   (bug 5250)
3647                 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
3648             }
3649         }
3650     }
3652     return false;
3655 function email_welcome_message_to_user($course, $user=NULL) {
3656     global $CFG, $USER;
3658     if (empty($user)) {
3659         if (!isloggedin()) {
3660             return false;
3661         }
3662         $user = $USER;
3663     }
3665     if (!empty($course->welcomemessage)) {
3666         $subject = get_string('welcometocourse', '', format_string($course->fullname));
3668         $a->coursename = $course->fullname;
3669         $a->profileurl = "$CFG->wwwroot/user/view.php?id=$USER->id&course=$course->id";
3670         //$message = get_string("welcometocoursetext", "", $a);
3671         $message = $course->welcomemessage;
3673         if (! $teacher = get_teacher($course->id)) {
3674             $teacher = get_admin();
3675         }
3676         email_to_user($user, $teacher, $subject, $message);
3677     }
3680 /// FILE HANDLING  /////////////////////////////////////////////
3683 /**
3684  * Makes an upload directory for a particular module.
3685  *
3686  * @uses $CFG
3687  * @param int $courseid The id of the course in question - maps to id field of 'course' table.
3688  * @return string|false Returns full path to directory if successful, false if not
3689  */
3690 function make_mod_upload_directory($courseid) {
3691     global $CFG;
3693     if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
3694         return false;
3695     }
3697     $strreadme = get_string('readme');
3699     if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
3700         copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3701     } else {
3702         copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3703     }
3704     return $moddata;
3707 /**
3708  * Returns current name of file on disk if it exists.
3709  *
3710  * @param string $newfile File to be verified
3711  * @return string Current name of file on disk if true
3712  */
3713 func