7a84222ca22db0e0525967519b1ff3d78c77a578
[moodle.git] / lib / moodlelib.php
1 <?php // $Id$
3 ///////////////////////////////////////////////////////////////////////////
4 //                                                                       //
5 // NOTICE OF COPYRIGHT                                                   //
6 //                                                                       //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment         //
8 //          http://moodle.org                                            //
9 //                                                                       //
10 // Copyright (C) 1999-2004  Martin Dougiamas  http://dougiamas.com       //
11 //                                                                       //
12 // This program is free software; you can redistribute it and/or modify  //
13 // it under the terms of the GNU General Public License as published by  //
14 // the Free Software Foundation; either version 2 of the License, or     //
15 // (at your option) any later version.                                   //
16 //                                                                       //
17 // This program is distributed in the hope that it will be useful,       //
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of        //
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
20 // GNU General Public License for more details:                          //
21 //                                                                       //
22 //          http://www.gnu.org/copyleft/gpl.html                         //
23 //                                                                       //
24 ///////////////////////////////////////////////////////////////////////////
26 /**
27  * moodlelib.php - Moodle main library
28  *
29  * Main library file of miscellaneous general-purpose Moodle functions.
30  * Other main libraries:
31  *  - weblib.php      - functions that produce web output
32  *  - datalib.php     - functions that access the database
33  * @author Martin Dougiamas
34  * @version $Id$
35  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
36  * @package moodlecore
37  */
39 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
41 /**
42  * Used by some scripts to check they are being called by Moodle
43  */
44 define('MOODLE_INTERNAL', true);
46 /**
47  * No groups used?
48  */
49 define('NOGROUPS', 0);
51 /**
52  * Groups used?
53  */
54 define('SEPARATEGROUPS', 1);
56 /**
57  * Groups visible?
58  */
59 define('VISIBLEGROUPS', 2);
61 /// Date and time constants ///
62 /**
63  * Time constant - the number of seconds in a week
64  */
65 define('WEEKSECS', 604800);
67 /**
68  * Time constant - the number of seconds in a day
69  */
70 define('DAYSECS', 86400);
72 /**
73  * Time constant - the number of seconds in an hour
74  */
75 define('HOURSECS', 3600);
77 /**
78  * Time constant - the number of seconds in a minute
79  */
80 define('MINSECS', 60);
82 /**
83  * Time constant - the number of minutes in a day
84  */
85 define('DAYMINS', 1440);
87 /**
88  * Time constant - the number of minutes in an hour
89  */
90 define('HOURMINS', 60);
92 /// Parameter constants - every call to optional_param(), required_param()  ///
93 /// or clean_param() should have a specified type of parameter.  //////////////
95 /**
96  * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
97  * originally was 0, but changed because we need to detect unknown
98  * parameter types and swiched order in clean_param().
99  */
100 define('PARAM_RAW', 666);
102 /**
103  * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
104  * It was one of the first types, that is why it is abused so much ;-)
105  */
106 define('PARAM_CLEAN',    0x0001);
108 /**
109  * PARAM_INT - integers only, use when expecting only numbers.
110  */
111 define('PARAM_INT',      0x0002);
113 /**
114  * PARAM_INTEGER - an alias for PARAM_INT
115  */
116 define('PARAM_INTEGER',  0x0002);
118 /**
119  * PARAM_NUMBER - a real/floating point number. 
120  */
121 define('PARAM_NUMBER',  0x000a);
123 /**
124  * PARAM_ALPHA - contains only english letters.
125  */
126 define('PARAM_ALPHA',    0x0004);
128 /**
129  * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
130  * @TODO: should we alias it to PARAM_ALPHANUM ?
131  */
132 define('PARAM_ACTION',   0x0004);
134 /**
135  * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
136  * @TODO: should we alias it to PARAM_ALPHANUM ?
137  */
138 define('PARAM_FORMAT',   0x0004);
140 /**
141  * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
142  */
143 define('PARAM_NOTAGS',   0x0008);
145  /**
146  * PARAM_MULTILANG - alias of PARAM_TEXT.
147  */
148 define('PARAM_MULTILANG',  0x0009);
150  /**
151  * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
152  */
153 define('PARAM_TEXT',  0x0009);
155 /**
156  * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
157  */
158 define('PARAM_FILE',     0x0010);
160 /**
161  * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
162  * note: the leading slash is not removed, window drive letter is not allowed
163  */
164 define('PARAM_PATH',     0x0020);
166 /**
167  * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
168  */
169 define('PARAM_HOST',     0x0040);
171 /**
172  * PARAM_URL - expected properly formatted URL.
173  */
174 define('PARAM_URL',      0x0080);
176 /**
177  * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the others! Implies PARAM_URL!)
178  */
179 define('PARAM_LOCALURL', 0x0180);
181 /**
182  * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
183  * use when you want to store a new file submitted by students
184  */
185 define('PARAM_CLEANFILE',0x0200);
187 /**
188  * PARAM_ALPHANUM - expected numbers and letters only.
189  */
190 define('PARAM_ALPHANUM', 0x0400);
192 /**
193  * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
194  */
195 define('PARAM_BOOL',     0x0800);
197 /**
198  * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
199  * note: do not forget to addslashes() before storing into database!
200  */
201 define('PARAM_CLEANHTML',0x1000);
203 /**
204  * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
205  * suitable for include() and require()
206  * @TODO: should we rename this function to PARAM_SAFEDIRS??
207  */
208 define('PARAM_ALPHAEXT', 0x2000);
210 /**
211  * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
212  */
213 define('PARAM_SAFEDIR',  0x4000);
215 /**
216  * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9.  Numbers and comma only.
217  */
218 define('PARAM_SEQUENCE',  0x8000);
220 /**
221  * PARAM_PEM - Privacy Enhanced Mail format
222  */
223 define('PARAM_PEM',      0x10000);
225 /**
226  * PARAM_BASE64 - Base 64 encoded format
227  */
228 define('PARAM_BASE64',   0x20000);
231 /// Page types ///
232 /**
233  * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
234  */
235 define('PAGE_COURSE_VIEW', 'course-view');
237 /// Debug levels ///
238 /** no warnings at all */
239 define ('DEBUG_NONE', 0);
240 /** E_ERROR | E_PARSE */
241 define ('DEBUG_MINIMAL', 5);
242 /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
243 define ('DEBUG_NORMAL', 15);
244 /** E_ALL without E_STRICT and E_RECOVERABLE_ERROR for now */
245 define ('DEBUG_ALL', 2047);
246 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
247 define ('DEBUG_DEVELOPER', 34815);
249 /**
250  * Blog access level constant declaration
251  */
252 define ('BLOG_USER_LEVEL', 1);
253 define ('BLOG_GROUP_LEVEL', 2);
254 define ('BLOG_COURSE_LEVEL', 3);
255 define ('BLOG_SITE_LEVEL', 4);
256 define ('BLOG_GLOBAL_LEVEL', 5);
260 /// PARAMETER HANDLING ////////////////////////////////////////////////////
262 /**
263  * Returns a particular value for the named variable, taken from
264  * POST or GET.  If the parameter doesn't exist then an error is
265  * thrown because we require this variable.
266  *
267  * This function should be used to initialise all required values
268  * in a script that are based on parameters.  Usually it will be
269  * used like this:
270  *    $id = required_param('id');
271  *
272  * @param string $parname the name of the page parameter we want
273  * @param int $type expected type of parameter
274  * @return mixed
275  */
276 function required_param($parname, $type=PARAM_CLEAN) {
278     // detect_unchecked_vars addition
279     global $CFG;
280     if (!empty($CFG->detect_unchecked_vars)) {
281         global $UNCHECKED_VARS;
282         unset ($UNCHECKED_VARS->vars[$parname]);
283     }
285     if (isset($_POST[$parname])) {       // POST has precedence
286         $param = $_POST[$parname];
287     } else if (isset($_GET[$parname])) {
288         $param = $_GET[$parname];
289     } else {
290         error('A required parameter ('.$parname.') was missing');
291     }
293     return clean_param($param, $type);
296 /**
297  * Returns a particular value for the named variable, taken from
298  * POST or GET, otherwise returning a given default.
299  *
300  * This function should be used to initialise all optional values
301  * in a script that are based on parameters.  Usually it will be
302  * used like this:
303  *    $name = optional_param('name', 'Fred');
304  *
305  * @param string $parname the name of the page parameter we want
306  * @param mixed  $default the default value to return if nothing is found
307  * @param int $type expected type of parameter
308  * @return mixed
309  */
310 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
312     // detect_unchecked_vars addition
313     global $CFG;
314     if (!empty($CFG->detect_unchecked_vars)) {
315         global $UNCHECKED_VARS;
316         unset ($UNCHECKED_VARS->vars[$parname]);
317     }
319     if (isset($_POST[$parname])) {       // POST has precedence
320         $param = $_POST[$parname];
321     } else if (isset($_GET[$parname])) {
322         $param = $_GET[$parname];
323     } else {
324         return $default;
325     }
327     return clean_param($param, $type);
330 /**
331  * Used by {@link optional_param()} and {@link required_param()} to
332  * clean the variables and/or cast to specific types, based on
333  * an options field.
334  * <code>
335  * $course->format = clean_param($course->format, PARAM_ALPHA);
336  * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
337  * </code>
338  *
339  * @uses $CFG
340  * @uses PARAM_CLEAN
341  * @uses PARAM_INT
342  * @uses PARAM_INTEGER
343  * @uses PARAM_ALPHA
344  * @uses PARAM_ALPHANUM
345  * @uses PARAM_NOTAGS
346  * @uses PARAM_ALPHAEXT
347  * @uses PARAM_BOOL
348  * @uses PARAM_SAFEDIR
349  * @uses PARAM_CLEANFILE
350  * @uses PARAM_FILE
351  * @uses PARAM_PATH
352  * @uses PARAM_HOST
353  * @uses PARAM_URL
354  * @uses PARAM_LOCALURL
355  * @uses PARAM_CLEANHTML
356  * @uses PARAM_SEQUENCE
357  * @param mixed $param the variable we are cleaning
358  * @param int $type expected format of param after cleaning.
359  * @return mixed
360  */
361 function clean_param($param, $type) {
363     global $CFG;
365     if (is_array($param)) {              // Let's loop
366         $newparam = array();
367         foreach ($param as $key => $value) {
368             $newparam[$key] = clean_param($value, $type);
369         }
370         return $newparam;
371     }
373     switch ($type) {
374         case PARAM_RAW:          // no cleaning at all
375             return $param;
377         case PARAM_CLEAN:        // General HTML cleaning, try to use more specific type if possible
378             if (is_numeric($param)) {
379                 return $param;
380             }
381             $param = stripslashes($param);   // Needed for kses to work fine
382             $param = clean_text($param);     // Sweep for scripts, etc
383             return addslashes($param);       // Restore original request parameter slashes
385         case PARAM_CLEANHTML:    // prepare html fragment for display, do not store it into db!!
386             $param = stripslashes($param);   // Remove any slashes
387             $param = clean_text($param);     // Sweep for scripts, etc
388             return trim($param);
390         case PARAM_INT:
391             return (int)$param;  // Convert to integer
393         case PARAM_NUMBER:
394             return (float)$param;  // Convert to integer
396         case PARAM_ALPHA:        // Remove everything not a-z
397             return eregi_replace('[^a-zA-Z]', '', $param);
399         case PARAM_ALPHANUM:     // Remove everything not a-zA-Z0-9
400             return eregi_replace('[^A-Za-z0-9]', '', $param);
402         case PARAM_ALPHAEXT:     // Remove everything not a-zA-Z/_-
403             return eregi_replace('[^a-zA-Z/_-]', '', $param);
405         case PARAM_SEQUENCE:     // Remove everything not 0-9,
406             return eregi_replace('[^0-9,]', '', $param);
408         case PARAM_BOOL:         // Convert to 1 or 0
409             $tempstr = strtolower($param);
410             if ($tempstr == 'on' or $tempstr == 'yes' ) {
411                 $param = 1;
412             } else if ($tempstr == 'off' or $tempstr == 'no') {
413                 $param = 0;
414             } else {
415                 $param = empty($param) ? 0 : 1;
416             }
417             return $param;
419         case PARAM_NOTAGS:       // Strip all tags
420             return strip_tags($param);
422         case PARAM_TEXT:    // leave only tags needed for multilang
423             return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
425         case PARAM_SAFEDIR:      // Remove everything not a-zA-Z0-9_-
426             return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
428         case PARAM_CLEANFILE:    // allow only safe characters
429             return clean_filename($param);
431         case PARAM_FILE:         // Strip all suspicious characters from filename
432             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
433             $param = ereg_replace('\.\.+', '', $param);
434             if($param == '.') {
435                 $param = '';
436             }
437             return $param;
439         case PARAM_PATH:         // Strip all suspicious characters from file path
440             $param = str_replace('\\\'', '\'', $param);
441             $param = str_replace('\\"', '"', $param);
442             $param = str_replace('\\', '/', $param);
443             $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
444             $param = ereg_replace('\.\.+', '', $param);
445             $param = ereg_replace('//+', '/', $param);
446             return ereg_replace('/(\./)+', '/', $param);
448         case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
449             preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
450             // match ipv4 dotted quad
451             if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
452                 // confirm values are ok
453                 if ( $match[0] > 255
454                      || $match[1] > 255
455                      || $match[3] > 255
456                      || $match[4] > 255 ) {
457                     // hmmm, what kind of dotted quad is this?
458                     $param = '';
459                 }
460             } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
461                        && !preg_match('/^[\.-]/',  $param) // no leading dots/hyphens
462                        && !preg_match('/[\.-]$/',  $param) // no trailing dots/hyphens
463                        ) {
464                 // all is ok - $param is respected
465             } else {
466                 // all is not ok...
467                 $param='';
468             }
469             return $param;
471         case PARAM_URL:          // allow safe ftp, http, mailto urls
472             include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
473             if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
474                 // all is ok, param is respected
475             } else {
476                 $param =''; // not really ok
477             }
478             return $param;
480         case PARAM_LOCALURL:     // allow http absolute, root relative and relative URLs within wwwroot
481             clean_param($param, PARAM_URL);
482             if (!empty($param)) {
483                 if (preg_match(':^/:', $param)) {
484                     // root-relative, ok!
485                 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
486                     // absolute, and matches our wwwroot
487                 } else {
488                     // relative - let's make sure there are no tricks
489                     if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
490                         // looks ok.
491                     } else {
492                         $param = '';
493                     }
494                 }
495             }
496             return $param;
497         case PARAM_PEM:
498             $param = trim($param);
499             // PEM formatted strings may contain letters/numbers and the symbols
500             // forward slash: /
501             // plus sign:     +
502             // equal sign:    =
503             // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
504             if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
505                 list($wholething, $body) = $matches;
506                 unset($wholething, $matches);
507                 $b64 = clean_param($body, PARAM_BASE64);
508                 if (!empty($b64)) {
509                     return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
510                 } else {
511                     return '';
512                 }
513             }
514             return '';
515         case PARAM_BASE64:
516             if (!empty($param)) {
517                 // PEM formatted strings may contain letters/numbers and the symbols
518                 // forward slash: /
519                 // plus sign:     +
520                 // equal sign:    =
521                 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
522                     return '';
523                 }
524                 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
525                 // Each line of base64 encoded data must be 64 characters in
526                 // length, except for the last line which may be less than (or
527                 // equal to) 64 characters long.
528                 for ($i=0, $j=count($lines); $i < $j; $i++) {
529                     if ($i + 1 == $j) {
530                         if (64 < strlen($lines[$i])) {
531                             return '';
532                         }
533                         continue;
534                     }
536                     if (64 != strlen($lines[$i])) {
537                         return '';
538                     }
539                 }
540                 return implode("\n",$lines);
541             } else {
542                 return '';
543             }
544         default:                 // throw error, switched parameters in optional_param or another serious problem
545             error("Unknown parameter type: $type");
546     }
551 /**
552  * Set a key in global configuration
553  *
554  * Set a key/value pair in both this session's {@link $CFG} global variable
555  * and in the 'config' database table for future sessions.
556  *
557  * Can also be used to update keys for plugin-scoped configs in config_plugin table.
558  * In that case it doesn't affect $CFG.
559  *
560  * @param string $name the key to set
561  * @param string $value the value to set (without magic quotes)
562  * @param string $plugin (optional) the plugin scope
563  * @uses $CFG
564  * @return bool
565  */
566 function set_config($name, $value, $plugin=NULL) {
567 /// No need for get_config because they are usually always available in $CFG
569     global $CFG;
571     if (empty($plugin)) {
572         $CFG->$name = $value;  // So it's defined for this invocation at least
574         if (get_field('config', 'name', 'name', $name)) {
575             return set_field('config', 'value', addslashes($value), 'name', $name);
576         } else {
577             $config = new object();
578             $config->name = $name;
579             $config->value = addslashes($value);
580             return insert_record('config', $config);
581         }
582     } else { // plugin scope
583         if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
584             return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
585         } else {
586             $config = new object();
587             $config->plugin = addslashes($plugin);
588             $config->name   = $name;
589             $config->value  = $value;
590             return insert_record('config_plugins', $config);
591         }
592     }
595 /**
596  * Get configuration values from the global config table
597  * or the config_plugins table.
598  *
599  * If called with no parameters it will do the right thing
600  * generating $CFG safely from the database without overwriting
601  * existing values.
602  *
603  * If called with 2 parameters it will return a $string single
604  * value or false of the value is not found.
605  *
606  * @param string $plugin
607  * @param string $name
608  * @uses $CFG
609  * @return hash-like object or single value
610  *
611  */
612 function get_config($plugin=NULL, $name=NULL) {
614     global $CFG;
616     if (!empty($name)) { // the user is asking for a specific value
617         if (!empty($plugin)) {
618             return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
619         } else {
620             return get_field('config', 'value', 'name', $name);
621         }
622     }
624     // the user is after a recordset
625     if (!empty($plugin)) {
626         if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
627             $configs = (array)$configs;
628             $localcfg = array();
629             foreach ($configs as $config) {
630                 $localcfg[$config->name] = $config->value;
631             }
632             return (object)$localcfg;
633         } else {
634             return false;
635         }
636     } else {
637         // this was originally in setup.php
638         if ($configs = get_records('config')) {
639             $localcfg = (array)$CFG;
640             foreach ($configs as $config) {
641                 if (!isset($localcfg[$config->name])) {
642                     $localcfg[$config->name] = $config->value;
643                 } else {
644                     if ($localcfg[$config->name] != $config->value ) {
645                         // complain if the DB has a different
646                         // value than config.php does
647                         error_log("\$CFG->{$config->name} in config.php ({$localcfg[$config->name]}) overrides database setting ({$config->value})");
648                     }
649                 }
650             }
652             $localcfg = (object)$localcfg;
653             return $localcfg;
654         } else {
655             // preserve $CFG if DB returns nothing or error
656             return $CFG;
657         }
659     }
662 /**
663  * Removes a key from global configuration
664  *
665  * @param string $name the key to set
666  * @param string $plugin (optional) the plugin scope
667  * @uses $CFG
668  * @return bool
669  */
670 function unset_config($name, $plugin=NULL) {
672     global $CFG;
674     unset($CFG->$name);
676     if (empty($plugin)) {
677         return delete_records('config', 'name', $name);
678     } else { 
679         return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
680     }
684 /**
685  * Refresh current $USER session global variable with all their current preferences.
686  * @uses $USER
687  */
688 function reload_user_preferences() {
690     global $USER;
692     if(empty($USER) || empty($USER->id)) {
693         return false;
694     }
696     unset($USER->preference);
698     if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
699         foreach ($preferences as $preference) {
700             $USER->preference[$preference->name] = $preference->value;
701         }
702     } else {
703             //return empty preference array to hold new values
704             $USER->preference = array();
705     }
708 /**
709  * Sets a preference for the current user
710  * Optionally, can set a preference for a different user object
711  * @uses $USER
712  * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
714  * @param string $name The key to set as preference for the specified user
715  * @param string $value The value to set forthe $name key in the specified user's record
716  * @param int $userid A moodle user ID
717  * @return bool
718  */
719 function set_user_preference($name, $value, $otheruser=NULL) {
721     global $USER;
723     if (empty($otheruser)){
724         if (!empty($USER) && !empty($USER->id)) {
725             $userid = $USER->id;
726         } else {
727             return false;
728         }
729     } else {
730         $userid = $otheruser;
731     }
733     if (empty($name)) {
734         return false;
735     }
737     if ($preference = get_record('user_preferences', 'userid', $userid, 'name', $name)) {
738         if (set_field('user_preferences', 'value', $value, 'id', $preference->id)) {
739             if ($userid == $USER->id) {
740                 $USER->preference[$name] = $value;
741             }
742             return true;
743         } else {
744             return false;
745         }
747     } else {
748         $preference->userid = $userid;
749         $preference->name   = $name;
750         $preference->value  = (string)$value;
751         if (insert_record('user_preferences', $preference)) {
752             if ($userid == $USER->id) {
753                 $USER->preference[$name] = $value;
754             }
755             return true;
756         } else {
757             return false;
758         }
759     }
762 /**
763  * Unsets a preference completely by deleting it from the database
764  * Optionally, can set a preference for a different user id
765  * @uses $USER
766  * @param string  $name The key to unset as preference for the specified user
767  * @param int $userid A moodle user ID
768  * @return bool
769  */
770 function unset_user_preference($name, $userid=NULL) {
772     global $USER;
774     if (empty($userid)){
775         if(!empty($USER) && !empty($USER->id)) {
776             $userid = $USER->id;
777         }
778         else {
779             return false;
780         }
781     }
783     //Delete the preference from $USER
784     if (isset($USER->preference[$name])) {
785         unset($USER->preference[$name]);
786     }
788     //Then from DB
789     return delete_records('user_preferences', 'userid', $userid, 'name', $name);
793 /**
794  * Sets a whole array of preferences for the current user
795  * @param array $prefarray An array of key/value pairs to be set
796  * @param int $userid A moodle user ID
797  * @return bool
798  */
799 function set_user_preferences($prefarray, $userid=NULL) {
801     global $USER;
803     if (!is_array($prefarray) or empty($prefarray)) {
804         return false;
805     }
807     if (empty($userid)){
808         if (!empty($USER) && !empty($USER->id)) {
809             $userid = NULL;  // Continue with the current user below
810         } else {
811             return false;    // No-one to set!
812         }
813     }
815     $return = true;
816     foreach ($prefarray as $name => $value) {
817         // The order is important; if the test for return is done first, then
818         // if one function call fails all the remaining ones will be "optimized away"
819         $return = set_user_preference($name, $value, $userid) and $return;
820     }
821     return $return;
824 /**
825  * If no arguments are supplied this function will return
826  * all of the current user preferences as an array.
827  * If a name is specified then this function
828  * attempts to return that particular preference value.  If
829  * none is found, then the optional value $default is returned,
830  * otherwise NULL.
831  * @param string $name Name of the key to use in finding a preference value
832  * @param string $default Value to be returned if the $name key is not set in the user preferences
833  * @param int $userid A moodle user ID
834  * @uses $USER
835  * @return string
836  */
837 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
839     global $USER;
841     if (empty($userid)) {   // assume current user
842         if (empty($USER->preference)) {
843             return $default;              // Default value (or NULL)
844         }
845         if (empty($name)) {
846             return $USER->preference;     // Whole array
847         }
848         if (!isset($USER->preference[$name])) {
849             return $default;              // Default value (or NULL)
850         }
851         return $USER->preference[$name];  // The single value
853     } else {
854         $preference = get_records_menu('user_preferences', 'userid', $userid, 'name', 'name,value');
856         if (empty($name)) {
857             return $preference;
858         }
859         if (!isset($preference[$name])) {
860             return $default;              // Default value (or NULL)
861         }
862         return $preference[$name];        // The single value
863     }
867 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
869 /**
870  * Given date parts in user time produce a GMT timestamp.
871  *
872  * @param int $year The year part to create timestamp of
873  * @param int $month The month part to create timestamp of
874  * @param int $day The day part to create timestamp of
875  * @param int $hour The hour part to create timestamp of
876  * @param int $minute The minute part to create timestamp of
877  * @param int $second The second part to create timestamp of
878  * @param float $timezone ?
879  * @param bool $applydst ?
880  * @return int timestamp
881  * @todo Finish documenting this function
882  */
883 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
885     $timezone = get_user_timezone_offset($timezone);
887     if (abs($timezone) > 13) {
888         $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
889     } else {
890         $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
891         $time = usertime($time, $timezone);
892         if($applydst) {
893             $time -= dst_offset_on($time);
894         }
895     }
897     return $time;
901 /**
902  * Given an amount of time in seconds, returns string
903  * formatted nicely as months, days, hours etc as needed
904  *
905  * @uses MINSECS
906  * @uses HOURSECS
907  * @uses DAYSECS
908  * @param int $totalsecs ?
909  * @param array $str ?
910  * @return string
911  * @todo Finish documenting this function
912  */
913  function format_time($totalsecs, $str=NULL) {
915     $totalsecs = abs($totalsecs);
917     if (!$str) {  // Create the str structure the slow way
918         $str->day   = get_string('day');
919         $str->days  = get_string('days');
920         $str->hour  = get_string('hour');
921         $str->hours = get_string('hours');
922         $str->min   = get_string('min');
923         $str->mins  = get_string('mins');
924         $str->sec   = get_string('sec');
925         $str->secs  = get_string('secs');
926     }
928     $days      = floor($totalsecs/DAYSECS);
929     $remainder = $totalsecs - ($days*DAYSECS);
930     $hours     = floor($remainder/HOURSECS);
931     $remainder = $remainder - ($hours*HOURSECS);
932     $mins      = floor($remainder/MINSECS);
933     $secs      = $remainder - ($mins*MINSECS);
935     $ss = ($secs == 1)  ? $str->sec  : $str->secs;
936     $sm = ($mins == 1)  ? $str->min  : $str->mins;
937     $sh = ($hours == 1) ? $str->hour : $str->hours;
938     $sd = ($days == 1)  ? $str->day  : $str->days;
940     $odays = '';
941     $ohours = '';
942     $omins = '';
943     $osecs = '';
945     if ($days)  $odays  = $days .' '. $sd;
946     if ($hours) $ohours = $hours .' '. $sh;
947     if ($mins)  $omins  = $mins .' '. $sm;
948     if ($secs)  $osecs  = $secs .' '. $ss;
950     if ($days)  return $odays .' '. $ohours;
951     if ($hours) return $ohours .' '. $omins;
952     if ($mins)  return $omins .' '. $osecs;
953     if ($secs)  return $osecs;
954     return get_string('now');
957 /**
958  * Returns a formatted string that represents a date in user time
959  * <b>WARNING: note that the format is for strftime(), not date().</b>
960  * Because of a bug in most Windows time libraries, we can't use
961  * the nicer %e, so we have to use %d which has leading zeroes.
962  * A lot of the fuss in the function is just getting rid of these leading
963  * zeroes as efficiently as possible.
964  *
965  * If parameter fixday = true (default), then take off leading
966  * zero from %d, else mantain it.
967  *
968  * @uses HOURSECS
969  * @param  int $date timestamp in GMT
970  * @param string $format strftime format
971  * @param float $timezone
972  * @param bool $fixday If true (default) then the leading
973  * zero from %d is removed. If false then the leading zero is mantained.
974  * @return string
975  */
976 function userdate($date, $format='', $timezone=99, $fixday = true) {
978     global $CFG;
980     static $strftimedaydatetime;
982     if ($format == '') {
983         if (empty($strftimedaydatetime)) {
984             $strftimedaydatetime = get_string('strftimedaydatetime');
985         }
986         $format = $strftimedaydatetime;
987     }
989     if (!empty($CFG->nofixday)) {  // Config.php can force %d not to be fixed.
990         $fixday = false;
991     } else if ($fixday) {
992         $formatnoday = str_replace('%d', 'DD', $format);
993         $fixday = ($formatnoday != $format);
994     }
996     $date += dst_offset_on($date);
998     $timezone = get_user_timezone_offset($timezone);
1000     if (abs($timezone) > 13) {   /// Server time
1001         if ($fixday) {
1002             $datestring = strftime($formatnoday, $date);
1003             $daystring  = str_replace(' 0', '', strftime(' %d', $date));
1004             $datestring = str_replace('DD', $daystring, $datestring);
1005         } else {
1006             $datestring = strftime($format, $date);
1007         }
1008     } else {
1009         $date += (int)($timezone * 3600);
1010         if ($fixday) {
1011             $datestring = gmstrftime($formatnoday, $date);
1012             $daystring  = str_replace(' 0', '', gmstrftime(' %d', $date));
1013             $datestring = str_replace('DD', $daystring, $datestring);
1014         } else {
1015             $datestring = gmstrftime($format, $date);
1016         }
1017     }
1019 /// If we are running under Windows convert from windows encoding to UTF-8
1020 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1022    if ($CFG->ostype == 'WINDOWS') {
1023        if ($localewincharset = get_string('localewincharset')) {
1024            $textlib = textlib_get_instance();
1025            $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1026        }
1027    }
1029     return $datestring;
1032 /**
1033  * Given a $time timestamp in GMT (seconds since epoch),
1034  * returns an array that represents the date in user time
1035  *
1036  * @uses HOURSECS
1037  * @param int $time Timestamp in GMT
1038  * @param float $timezone ?
1039  * @return array An array that represents the date in user time
1040  * @todo Finish documenting this function
1041  */
1042 function usergetdate($time, $timezone=99) {
1044     $timezone = get_user_timezone_offset($timezone);
1046     if (abs($timezone) > 13) {    // Server time
1047         return getdate($time);
1048     }
1050     // There is no gmgetdate so we use gmdate instead
1051     $time += dst_offset_on($time);
1052     $time += intval((float)$timezone * HOURSECS);
1054     $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
1056     list(
1057         $getdate['seconds'],
1058         $getdate['minutes'],
1059         $getdate['hours'],
1060         $getdate['mday'],
1061         $getdate['mon'],
1062         $getdate['year'],
1063         $getdate['wday'],
1064         $getdate['yday'],
1065         $getdate['weekday'],
1066         $getdate['month']
1067     ) = explode('_', $datestring);
1069     return $getdate;
1072 /**
1073  * Given a GMT timestamp (seconds since epoch), offsets it by
1074  * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1075  *
1076  * @uses HOURSECS
1077  * @param  int $date Timestamp in GMT
1078  * @param float $timezone
1079  * @return int
1080  */
1081 function usertime($date, $timezone=99) {
1083     $timezone = get_user_timezone_offset($timezone);
1085     if (abs($timezone) > 13) {
1086         return $date;
1087     }
1088     return $date - (int)($timezone * HOURSECS);
1091 /**
1092  * Given a time, return the GMT timestamp of the most recent midnight
1093  * for the current user.
1094  *
1095  * @param int $date Timestamp in GMT
1096  * @param float $timezone ?
1097  * @return ?
1098  */
1099 function usergetmidnight($date, $timezone=99) {
1101     $timezone = get_user_timezone_offset($timezone);
1102     $userdate = usergetdate($date, $timezone);
1104     // Time of midnight of this user's day, in GMT
1105     return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1109 /**
1110  * Returns a string that prints the user's timezone
1111  *
1112  * @param float $timezone The user's timezone
1113  * @return string
1114  */
1115 function usertimezone($timezone=99) {
1117     $tz = get_user_timezone($timezone);
1119     if (!is_float($tz)) {
1120         return $tz;
1121     }
1123     if(abs($tz) > 13) { // Server time
1124         return get_string('serverlocaltime');
1125     }
1127     if($tz == intval($tz)) {
1128         // Don't show .0 for whole hours
1129         $tz = intval($tz);
1130     }
1132     if($tz == 0) {
1133         return 'GMT';
1134     }
1135     else if($tz > 0) {
1136         return 'GMT+'.$tz;
1137     }
1138     else {
1139         return 'GMT'.$tz;
1140     }
1144 /**
1145  * Returns a float which represents the user's timezone difference from GMT in hours
1146  * Checks various settings and picks the most dominant of those which have a value
1147  *
1148  * @uses $CFG
1149  * @uses $USER
1150  * @param float $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
1151  * @return int
1152  */
1153 function get_user_timezone_offset($tz = 99) {
1155     global $USER, $CFG;
1157     $tz = get_user_timezone($tz);
1159     if (is_float($tz)) {
1160         return $tz;
1161     } else {
1162         $tzrecord = get_timezone_record($tz);
1163         if (empty($tzrecord)) {
1164             return 99.0;
1165         }
1166         return (float)$tzrecord->gmtoff / HOURMINS;
1167     }
1170 /**
1171  * Returns a float or a string which denotes the user's timezone
1172  * A float value means that a simple offset from GMT is used, while a string (it will be the name of a timezone in the database)
1173  * means that for this timezone there are also DST rules to be taken into account
1174  * Checks various settings and picks the most dominant of those which have a value
1175  *
1176  * @uses $USER
1177  * @uses $CFG
1178  * @param float $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
1179  * @return mixed
1180  */
1181 function get_user_timezone($tz = 99) {
1182     global $USER, $CFG;
1184     $timezones = array(
1185         $tz,
1186         isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1187         isset($USER->timezone) ? $USER->timezone : 99,
1188         isset($CFG->timezone) ? $CFG->timezone : 99,
1189         );
1191     $tz = 99;
1193     while(($tz == '' || $tz == 99) && $next = each($timezones)) {
1194         $tz = $next['value'];
1195     }
1197     return is_numeric($tz) ? (float) $tz : $tz;
1200 /**
1201  * ?
1202  *
1203  * @uses $CFG
1204  * @uses $db
1205  * @param string $timezonename ?
1206  * @return object
1207  */
1208 function get_timezone_record($timezonename) {
1209     global $CFG, $db;
1210     static $cache = NULL;
1212     if ($cache === NULL) {
1213         $cache = array();
1214     }
1216     if (isset($cache[$timezonename])) {
1217         return $cache[$timezonename];
1218     }
1220     return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1221                                       WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1224 /**
1225  * ?
1226  *
1227  * @uses $CFG
1228  * @uses $USER
1229  * @param ? $fromyear ?
1230  * @param ? $to_year ?
1231  * @return bool
1232  */
1233 function calculate_user_dst_table($from_year = NULL, $to_year = NULL) {
1234     global $CFG, $SESSION;
1236     $usertz = get_user_timezone();
1238     if (is_float($usertz)) {
1239         // Trivial timezone, no DST
1240         return false;
1241     }
1243     if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1244         // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1245         unset($SESSION->dst_offsets);
1246         unset($SESSION->dst_range);
1247     }
1249     if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1250         // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1251         // This will be the return path most of the time, pretty light computationally
1252         return true;
1253     }
1255     // Reaching here means we either need to extend our table or create it from scratch
1257     // Remember which TZ we calculated these changes for
1258     $SESSION->dst_offsettz = $usertz;
1260     if(empty($SESSION->dst_offsets)) {
1261         // If we 're creating from scratch, put the two guard elements in there
1262         $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1263     }
1264     if(empty($SESSION->dst_range)) {
1265         // If creating from scratch
1266         $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1267         $to   = min((empty($to_year)   ? intval(date('Y')) + 3 : $to_year),   2035);
1269         // Fill in the array with the extra years we need to process
1270         $yearstoprocess = array();
1271         for($i = $from; $i <= $to; ++$i) {
1272             $yearstoprocess[] = $i;
1273         }
1275         // Take note of which years we have processed for future calls
1276         $SESSION->dst_range = array($from, $to);
1277     }
1278     else {
1279         // If needing to extend the table, do the same
1280         $yearstoprocess = array();
1282         $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1283         $to   = min((empty($to_year)   ? $SESSION->dst_range[1] : $to_year),   2035);
1285         if($from < $SESSION->dst_range[0]) {
1286             // Take note of which years we need to process and then note that we have processed them for future calls
1287             for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1288                 $yearstoprocess[] = $i;
1289             }
1290             $SESSION->dst_range[0] = $from;
1291         }
1292         if($to > $SESSION->dst_range[1]) {
1293             // Take note of which years we need to process and then note that we have processed them for future calls
1294             for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1295                 $yearstoprocess[] = $i;
1296             }
1297             $SESSION->dst_range[1] = $to;
1298         }
1299     }
1301     if(empty($yearstoprocess)) {
1302         // This means that there was a call requesting a SMALLER range than we have already calculated
1303         return true;
1304     }
1306     // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1307     // Also, the array is sorted in descending timestamp order!
1309     // Get DB data
1310     $presetrecords = get_records('timezone', 'name', $usertz, 'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, std_startday, std_weekday, std_skipweeks, std_time');
1311     if(empty($presetrecords)) {
1312         return false;
1313     }
1315     // Remove ending guard (first element of the array)
1316     reset($SESSION->dst_offsets);
1317     unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1319     // Add all required change timestamps
1320     foreach($yearstoprocess as $y) {
1321         // Find the record which is in effect for the year $y
1322         foreach($presetrecords as $year => $preset) {
1323             if($year <= $y) {
1324                 break;
1325             }
1326         }
1328         $changes = dst_changes_for_year($y, $preset);
1330         if($changes === NULL) {
1331             continue;
1332         }
1333         if($changes['dst'] != 0) {
1334             $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1335         }
1336         if($changes['std'] != 0) {
1337             $SESSION->dst_offsets[$changes['std']] = 0;
1338         }
1339     }
1341     // Put in a guard element at the top
1342     $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1343     $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1345     // Sort again
1346     krsort($SESSION->dst_offsets);
1348     return true;
1351 function dst_changes_for_year($year, $timezone) {
1353     if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1354         return NULL;
1355     }
1357     $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1358     $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1360     list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1361     list($std_hour, $std_min) = explode(':', $timezone->std_time);
1363     $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1364     $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1366     // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1367     // This has the advantage of being able to have negative values for hour, i.e. for timezones
1368     // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1370     $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1371     $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1373     return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1376 // $time must NOT be compensated at all, it has to be a pure timestamp
1377 function dst_offset_on($time) {
1378     global $SESSION;
1380     if(!calculate_user_dst_table() || empty($SESSION->dst_offsets)) {
1381         return 0;
1382     }
1384     reset($SESSION->dst_offsets);
1385     while(list($from, $offset) = each($SESSION->dst_offsets)) {
1386         if($from <= $time) {
1387             break;
1388         }
1389     }
1391     // This is the normal return path
1392     if($offset !== NULL) {
1393         return $offset;
1394     }
1396     // Reaching this point means we haven't calculated far enough, do it now:
1397     // Calculate extra DST changes if needed and recurse. The recursion always
1398     // moves toward the stopping condition, so will always end.
1400     if($from == 0) {
1401         // We need a year smaller than $SESSION->dst_range[0]
1402         if($SESSION->dst_range[0] == 1971) {
1403             return 0;
1404         }
1405         calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL);
1406         return dst_offset_on($time);
1407     }
1408     else {
1409         // We need a year larger than $SESSION->dst_range[1]
1410         if($SESSION->dst_range[1] == 2035) {
1411             return 0;
1412         }
1413         calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5);
1414         return dst_offset_on($time);
1415     }
1418 function find_day_in_month($startday, $weekday, $month, $year) {
1420     $daysinmonth = days_in_month($month, $year);
1422     if($weekday == -1) {
1423         // Don't care about weekday, so return:
1424         //    abs($startday) if $startday != -1
1425         //    $daysinmonth otherwise
1426         return ($startday == -1) ? $daysinmonth : abs($startday);
1427     }
1429     // From now on we 're looking for a specific weekday
1431     // Give "end of month" its actual value, since we know it
1432     if($startday == -1) {
1433         $startday = -1 * $daysinmonth;
1434     }
1436     // Starting from day $startday, the sign is the direction
1438     if($startday < 1) {
1440         $startday = abs($startday);
1441         $lastmonthweekday  = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1443         // This is the last such weekday of the month
1444         $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1445         if($lastinmonth > $daysinmonth) {
1446             $lastinmonth -= 7;
1447         }
1449         // Find the first such weekday <= $startday
1450         while($lastinmonth > $startday) {
1451             $lastinmonth -= 7;
1452         }
1454         return $lastinmonth;
1456     }
1457     else {
1459         $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1461         $diff = $weekday - $indexweekday;
1462         if($diff < 0) {
1463             $diff += 7;
1464         }
1466         // This is the first such weekday of the month equal to or after $startday
1467         $firstfromindex = $startday + $diff;
1469         return $firstfromindex;
1471     }
1474 /**
1475  * Calculate the number of days in a given month
1476  *
1477  * @param int $month The month whose day count is sought
1478  * @param int $year The year of the month whose day count is sought
1479  * @return int
1480  */
1481 function days_in_month($month, $year) {
1482    return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1485 /**
1486  * Calculate the position in the week of a specific calendar day
1487  *
1488  * @param int $day The day of the date whose position in the week is sought
1489  * @param int $month The month of the date whose position in the week is sought
1490  * @param int $year The year of the date whose position in the week is sought
1491  * @return int
1492  */
1493 function dayofweek($day, $month, $year) {
1494     // I wonder if this is any different from
1495     // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1496     return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1499 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1501 /**
1502  * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1503  * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1504  * sesskey string if $USER exists, or boolean false if not.
1505  *
1506  * @uses $USER
1507  * @return string
1508  */
1509 function sesskey() {
1510     global $USER;
1512     if(!isset($USER)) {
1513         return false;
1514     }
1516     if (empty($USER->sesskey)) {
1517         $USER->sesskey = random_string(10);
1518     }
1520     return $USER->sesskey;
1524 /**
1525  * For security purposes, this function will check that the currently
1526  * given sesskey (passed as a parameter to the script or this function)
1527  * matches that of the current user.
1528  *
1529  * @param string $sesskey optionally provided sesskey
1530  * @return bool
1531  */
1532 function confirm_sesskey($sesskey=NULL) {
1533     global $USER;
1535     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1536         return true;
1537     }
1539     if (empty($sesskey)) {
1540         $sesskey = required_param('sesskey', PARAM_RAW);  // Check script parameters
1541     }
1543     if (!isset($USER->sesskey)) {
1544         return false;
1545     }
1547     return ($USER->sesskey === $sesskey);
1550 /**
1551  * Setup all global $CFG course variables, set locale and also 
1552  * @param mixed $courseorid id of the course or course object
1553  */
1554 function course_setup($courseorid=0) {
1555     global $COURSE, $HTTPSPAGEREQUIRED, $CFG;
1557 /// Redefine global $COURSE if needed
1558     if (empty($courseorid)) {
1559         // no change in global $COURSE - for backwards compatibiltiy
1560         // if require_rogin() used after require_login($courseid); 
1561     } else if (is_object($courseorid)) {
1562         $COURSE = clone($courseorid);
1563     } else {
1564         global $course; // used here only to prevent repeated fetching from DB - may be removed later
1565         if ($course->id == $courseorid) {
1566             $COURSE = clone($course);
1567         } else {
1568             if (!$COURSE = get_record('course', 'id', $courseorid)) {
1569                 error('Invalid course ID');
1570             }
1571         }
1572     }
1574 /// set locale - we should use $COURSE->lang directly in the future
1575 /// $CFG->courselang is now used in cron and chat to override current language and locale
1576     if ($COURSE->id == SITEID or empty($COURSE->lang)) {
1577         unset($CFG->courselang);
1578     } else {
1579         $CFG->courselang = $COURSE->lang;
1580     }
1581     moodle_setlocale();
1583 /// setup themes - $COURSE->theme should be used instead of $CFG->coursetheme soon 
1584     if ($COURSE->id == SITEID or empty($CFG->allowcoursethemes) or empty($COURSE->theme)) {
1585         unset($CFG->coursetheme);
1586     } else {
1587         $CFG->coursetheme = $COURSE->theme;
1588     }
1589     theme_setup();
1591 /// We have to change some URLs in styles if we are in a $HTTPSPAGEREQUIRED page
1592 /// in case theme changed after call to httpsrequired();
1593     if (!empty($HTTPSPAGEREQUIRED)) {
1594         $CFG->themewww = str_replace('http:', 'https:', $CFG->themewww);
1595         $CFG->pixpath = str_replace('http:', 'https:', $CFG->pixpath);
1596         $CFG->modpixpath = str_replace('http:', 'https:', $CFG->modpixpath);
1597         foreach ($CFG->stylesheets as $key => $stylesheet) {
1598             $CFG->stylesheets[$key] = str_replace('http:', 'https:', $stylesheet);
1599         }
1600     }
1603 /**
1604  * This function checks that the current user is logged in and has the
1605  * required privileges
1606  *
1607  * This function checks that the current user is logged in, and optionally
1608  * whether they are allowed to be in a particular course and view a particular
1609  * course module.
1610  * If they are not logged in, then it redirects them to the site login unless
1611  * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1612  * case they are automatically logged in as guests.
1613  * If $courseid is given and the user is not enrolled in that course then the
1614  * user is redirected to the course enrolment page.
1615  * If $cm is given and the coursemodule is hidden and the user is not a teacher
1616  * in the course then the user is redirected to the course home page.
1617  *
1618  * @uses $CFG
1619  * @uses $SESSION
1620  * @uses $USER
1621  * @uses $FULLME
1622  * @uses SITEID
1623  * @uses $COURSE
1624  * @param mixed $courseorid id of the course or course object
1625  * @param bool $autologinguest
1626  * @param object $cm course module object
1627  */
1628 function require_login($courseorid=0, $autologinguest=true, $cm=null) {
1630     global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1632 /// setup global $COURSE, themes, language and locale
1633     course_setup($courseorid);
1635 /// If the user is not even logged in yet then make sure they are
1636     if (!isloggedin()) {
1637         //NOTE: $USER->site check was obsoleted by session test cookie,
1638         //      $USER->confirmed test is in login/index.php
1639         $SESSION->wantsurl = $FULLME;
1640         if (!empty($_SERVER['HTTP_REFERER'])) {
1641             $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
1642         }
1643         if ($autologinguest and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1644             $loginguest = '?loginguest=true';
1645         } else {
1646             $loginguest = '';
1647         }
1648         if (empty($CFG->loginhttps) or $autologinguest) { //do not require https for guest logins
1649             redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1650         } else {
1651             $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1652             redirect($wwwroot .'/login/index.php');
1653         }
1654         exit;
1655     }
1657 /// check whether the user should be changing password
1658     $userauth = get_auth_plugin($USER->auth);
1659     if (!empty($USER->preference['auth_forcepasswordchange'])){
1660         if ($userauth->can_change_password()) {
1661             $SESSION->wantsurl = $FULLME;
1662             if (empty($CFG->loginhttps)) {
1663                 redirect($CFG->wwwroot .'/login/change_password.php');
1664             } else {
1665                 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1666                 redirect($wwwroot .'/login/change_password.php');
1667             }
1668         } else if($userauth->change_password_url()) {
1669             redirect($userauth->change_password_url());
1670         } else {
1671             error('You cannot proceed without changing your password.
1672                    However there is no available page for changing it.
1673                    Please contact your Moodle Administrator.');
1674         }
1675     }
1677 /// Check that the user account is properly set up
1678     if (user_not_fully_set_up($USER)) {
1679         $SESSION->wantsurl = $FULLME;
1680         redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1681     }
1683 /// Make sure current IP matches the one for this session (if required)
1684     if (!empty($CFG->tracksessionip)) {
1685         if ($USER->sessionIP != md5(getremoteaddr())) {
1686             error(get_string('sessionipnomatch', 'error'));
1687         }
1688     }
1690 /// Make sure the USER has a sesskey set up.  Used for checking script parameters.
1691     sesskey();
1693     // Check that the user has agreed to a site policy if there is one
1694     if (!empty($CFG->sitepolicy)) {
1695         if (!$USER->policyagreed) {
1696             $SESSION->wantsurl = $FULLME;
1697             redirect($CFG->wwwroot .'/user/policy.php');
1698         }
1699     }
1701 /// If the site is currently under maintenance, then print a message
1702     if (!has_capability('moodle/site:config',get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1703         if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1704             print_maintenance_message();
1705             exit;
1706         }
1707     }
1710     if ($COURSE->id == SITEID) {
1711 /// We can eliminate hidden site activities straight away
1712         if (!empty($cm) && !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', 
1713                                                       get_context_instance(CONTEXT_SYSTEM, SITEID))) {
1714             redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
1715         }
1716         return;
1718     } else { 
1719 /// Check if the user can be in a particular course
1720         if (!$context = get_context_instance(CONTEXT_COURSE, $COURSE->id)) {
1721             print_error('nocontext');
1722         }
1724         if (empty($USER->switchrole[$context->id]) &&
1725             !($COURSE->visible && course_parent_visible($COURSE)) &&
1726                !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $COURSE->id)) ){
1727             print_header_simple();
1728             notice(get_string('coursehidden'), $CFG->wwwroot .'/');
1729         }    
1730         
1731     /// Non-guests who don't currently have access, check if they can be allowed in as a guest
1733         if ($USER->username != 'guest' and !has_capability('moodle/course:view', $context)) {
1734             if ($COURSE->guest == 1) {
1735                  // Temporarily assign them guest role for this context,
1736                  // if it fails user is asked to enrol
1737                  load_guest_role($context);
1738             }
1739         }
1741     /// If the user is a guest then treat them according to the course policy about guests
1743         if (has_capability('moodle/legacy:guest', $context, NULL, false)) {
1744             switch ($COURSE->guest) {    /// Check course policy about guest access
1746                 case 1:    /// Guests always allowed 
1747                     if (!has_capability('moodle/course:view', $context)) {    // Prohibited by capability
1748                         print_header_simple();
1749                         notice(get_string('guestsnotallowed', '', $COURSE->fullname), "$CFG->wwwroot/login/index.php");
1750                     }
1751                     if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
1752                         redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, 
1753                                  get_string('activityiscurrentlyhidden'));
1754                     }
1756                     return;   // User is allowed to see this course
1758                     break;
1760                 case 2:    /// Guests allowed with key 
1761                     if (!empty($USER->enrolkey[$COURSE->id])) {   // Set by enrol/manual/enrol.php
1762                         return true;
1763                     }
1764                     //  otherwise drop through to logic below (--> enrol.php)
1765                     break;
1767                 default:    /// Guests not allowed
1768                     print_header_simple('', '', get_string('loggedinasguest'));
1769                     if (empty($USER->switchrole[$context->id])) {  // Normal guest
1770                         notice(get_string('guestsnotallowed', '', $COURSE->fullname), "$CFG->wwwroot/login/index.php");
1771                     } else {
1772                         notify(get_string('guestsnotallowed', '', $COURSE->fullname));
1773                         echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
1774                         print_footer($COURSE);
1775                         exit;
1776                     }
1777                     break;
1778             }
1780     /// For non-guests, check if they have course view access
1782         } else if (has_capability('moodle/course:view', $context)) {
1783             if (!empty($USER->realuser)) {   // Make sure the REAL person can also access this course
1784                 if (!has_capability('moodle/course:view', $context, $USER->realuser)) {
1785                     print_header_simple();
1786                     notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
1787                 }
1788             }
1790         /// Make sure they can read this activity too, if specified
1792             if (!empty($cm) and !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) { 
1793                 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
1794             }
1795             return;   // User is allowed to see this course
1797         }
1800     /// Currently not enrolled in the course, so see if they want to enrol
1801         $SESSION->wantsurl = $FULLME;
1802         redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
1803         die;
1804     }
1809 /**
1810  * This function just makes sure a user is logged out.
1811  *
1812  * @uses $CFG
1813  * @uses $USER
1814  */
1815 function require_logout() {
1817     global $USER, $CFG, $SESSION;
1819     if (isset($USER) and isset($USER->id)) {
1820         add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
1822         if ($USER->auth == 'cas' && !empty($CFG->cas_enabled)) {
1823             require($CFG->dirroot.'/auth/cas/logout.php');
1824         }
1825         
1826         if (extension_loaded('openssl')) {
1827             require($CFG->dirroot.'/auth/mnet/auth.php');
1828             $authplugin = new auth_plugin_mnet();
1829             $authplugin->logout();
1830         }
1831     }
1833     if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
1834         // This method is just to try to avoid silly warnings from PHP 4.3.0
1835         session_unregister("USER");
1836         session_unregister("SESSION");
1837     }
1839     setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath);
1840     unset($_SESSION['USER']);
1841     unset($_SESSION['SESSION']);
1843     unset($SESSION);
1844     unset($USER);
1848 /**
1849  * This is a weaker version of {@link require_login()} which only requires login
1850  * when called from within a course rather than the site page, unless
1851  * the forcelogin option is turned on.
1852  *
1853  * @uses $CFG
1854  * @param mixed $courseorid The course object or id in question
1855  * @param bool $autologinguest Allow autologin guests if that is wanted
1856  * @param object $cm Course activity module if known
1857  */
1858 function require_course_login($courseorid, $autologinguest=true, $cm=null) {
1859     global $CFG;
1860     if (!empty($CFG->forcelogin)) {
1861         // login required for both SITE and courses
1862         require_login($courseorid, $autologinguest, $cm);
1863     } else if ((is_object($courseorid) and $courseorid->id == SITEID) or $courseorid == SITEID) {
1864         //login for SITE not required
1865     } else {
1866         // course login always required
1867         require_login($courseorid, $autologinguest, $cm);
1868     }
1871 /**
1872  * Modify the user table by setting the currently logged in user's
1873  * last login to now.
1874  *
1875  * @uses $USER
1876  * @return bool
1877  */
1878 function update_user_login_times() {
1879     global $USER;
1881     $user = new object();
1882     $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
1883     $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
1885     $user->id = $USER->id;
1887     return update_record('user', $user);
1890 /**
1891  * Determines if a user has completed setting up their account.
1892  *
1893  * @param user $user A {@link $USER} object to test for the existance of a valid name and email
1894  * @return bool
1895  */
1896 function user_not_fully_set_up($user) {
1897     return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
1900 function over_bounce_threshold($user) {
1902     global $CFG;
1904     if (empty($CFG->handlebounces)) {
1905         return false;
1906     }
1907     // set sensible defaults
1908     if (empty($CFG->minbounces)) {
1909         $CFG->minbounces = 10;
1910     }
1911     if (empty($CFG->bounceratio)) {
1912         $CFG->bounceratio = .20;
1913     }
1914     $bouncecount = 0;
1915     $sendcount = 0;
1916     if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1917         $bouncecount = $bounce->value;
1918     }
1919     if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1920         $sendcount = $send->value;
1921     }
1922     return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
1925 /**
1926  * @param $user - object containing an id
1927  * @param $reset - will reset the count to 0
1928  */
1929 function set_send_count($user,$reset=false) {
1930     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
1931         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1932         update_record('user_preferences',$pref);
1933     }
1934     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1935         // make a new one
1936         $pref->name = 'email_send_count';
1937         $pref->value = 1;
1938         $pref->userid = $user->id;
1939         insert_record('user_preferences',$pref, false);
1940     }
1943 /**
1944 * @param $user - object containing an id
1945  * @param $reset - will reset the count to 0
1946  */
1947 function set_bounce_count($user,$reset=false) {
1948     if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
1949         $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
1950         update_record('user_preferences',$pref);
1951     }
1952     else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
1953         // make a new one
1954         $pref->name = 'email_bounce_count';
1955         $pref->value = 1;
1956         $pref->userid = $user->id;
1957         insert_record('user_preferences',$pref, false);
1958     }
1961 /**
1962  * Keeps track of login attempts
1963  *
1964  * @uses $SESSION
1965  */
1966 function update_login_count() {
1968     global $SESSION;
1970     $max_logins = 10;
1972     if (empty($SESSION->logincount)) {
1973         $SESSION->logincount = 1;
1974     } else {
1975         $SESSION->logincount++;
1976     }
1978     if ($SESSION->logincount > $max_logins) {
1979         unset($SESSION->wantsurl);
1980         error(get_string('errortoomanylogins'));
1981     }
1984 /**
1985  * Resets login attempts
1986  *
1987  * @uses $SESSION
1988  */
1989 function reset_login_count() {
1990     global $SESSION;
1992     $SESSION->logincount = 0;
1995 function sync_metacourses() {
1997     global $CFG;
1999     if (!$courses = get_records('course', 'metacourse', 1)) {
2000         return;
2001     }
2003     foreach ($courses as $course) {
2004         sync_metacourse($course);
2005     }
2008 /**
2009  * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2010  * 
2011  * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2012  */
2013 function sync_metacourse($course) {
2014     global $CFG;
2016     // Check the course is valid.
2017     if (!is_object($course)) {
2018         if (!$course = get_record('course', 'id', $course)) {
2019             return false; // invalid course id
2020         }
2021     }
2022     
2023     // Check that we actually have a metacourse.
2024     if (empty($course->metacourse)) {
2025         return false;
2026     }
2028     // Get a list of roles that should not be synced.
2029     if ($CFG->nonmetacoursesyncroleids) {
2030         $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2031     } else { 
2032         $roleexclusions = '';
2033     }
2035     // Get the context of the metacourse.
2036     $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2038     // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2039     if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2040         $managers = array_keys($users);
2041     } else {
2042         $managers = array();
2043     }
2045     // Get assignments of a user to a role that exist in a child course, but
2046     // not in the meta coure. That is, get a list of the assignments that need to be made.
2047     if (!$assignments = get_records_sql("
2048             SELECT
2049                 ra.id, ra.roleid, ra.userid
2050             FROM
2051                 {$CFG->prefix}role_assignments ra,
2052                 {$CFG->prefix}context con,
2053                 {$CFG->prefix}course_meta cm
2054             WHERE
2055                 ra.contextid = con.id AND
2056                 con.contextlevel = " . CONTEXT_COURSE . " AND
2057                 con.instanceid = cm.child_course AND
2058                 cm.parent_course = {$course->id} AND
2059                 $roleexclusions
2060                 NOT EXISTS (
2061                     SELECT 1 FROM
2062                         {$CFG->prefix}role_assignments ra2
2063                     WHERE
2064                         ra2.userid = ra.userid AND
2065                         ra2.roleid = ra.roleid AND
2066                         ra2.contextid = {$context->id}
2067                 )
2068     ")) {
2069         $assignments = array();
2070     }
2072     // Get assignments of a user to a role that exist in the meta course, but
2073     // not in any child courses. That is, get a list of the unassignments that need to be made.
2074     if (!$unassignments = get_records_sql("
2075             SELECT
2076                 ra.id, ra.roleid, ra.userid
2077             FROM
2078                 {$CFG->prefix}role_assignments ra
2079             WHERE
2080                 ra.contextid = {$context->id} AND
2081                 $roleexclusions
2082                 NOT EXISTS (
2083                     SELECT 1 FROM
2084                         {$CFG->prefix}role_assignments ra2,
2085                         {$CFG->prefix}context con2,
2086                         {$CFG->prefix}course_meta cm
2087                     WHERE
2088                         ra2.userid = ra.userid AND
2089                         ra2.roleid = ra.roleid AND
2090                         ra2.contextid = con2.id AND
2091                         con2.contextlevel = " . CONTEXT_COURSE . " AND
2092                         con2.instanceid = cm.child_course AND
2093                         cm.parent_course = {$course->id}
2094                 )
2095     ")) {
2096         $unassignments = array();
2097     }
2099     $success = true;
2101     // Make the unassignments, if they are not managers.
2102     foreach ($unassignments as $unassignment) {
2103         if (!in_array($unassignment->userid, $managers)) {
2104             $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2105         }
2106     }
2108     // Make the assignments.
2109     foreach ($assignments as $assignment) {
2110         $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id) && $success;
2111     }
2113     return $success;
2114     
2115 // TODO: finish timeend and timestart
2116 // maybe we could rely on cron job to do the cleaning from time to time
2119 /**
2120  * Adds a record to the metacourse table and calls sync_metacoures
2121  */
2122 function add_to_metacourse ($metacourseid, $courseid) {
2124     if (!$metacourse = get_record("course","id",$metacourseid)) {
2125         return false;
2126     }
2128     if (!$course = get_record("course","id",$courseid)) {
2129         return false;
2130     }
2132     if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2133         $rec = new object();
2134         $rec->parent_course = $metacourseid;
2135         $rec->child_course = $courseid;
2136         if (!insert_record('course_meta',$rec)) {
2137             return false;
2138         }
2139         return sync_metacourse($metacourseid);
2140     }
2141     return true;
2145 /**
2146  * Removes the record from the metacourse table and calls sync_metacourse
2147  */
2148 function remove_from_metacourse($metacourseid, $courseid) {
2150     if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2151         return sync_metacourse($metacourseid);
2152     }
2153     return false;
2157 /**
2158  * Determines if a user is currently logged in
2159  *
2160  * @uses $USER
2161  * @return bool
2162  */
2163 function isloggedin() {
2164     global $USER;
2166     return (!empty($USER->id));
2169 /**
2170  * Determines if a user is logged in as real guest user with username 'guest'.
2171  * This function is similar to original isguest() in 1.6 and earlier.
2172  * Current isguest() is deprecated - do not use it anymore.
2173  *
2174  * @param $user mixed user object or id, $USER if not specified
2175  * @return bool true if user is the real guest user, false if not logged in or other user
2176  */
2177 function isguestuser($user=NULL) {
2178     global $USER;
2179     if ($user === NULL) {
2180         $user = $USER;
2181     } else if (is_numeric($user)) {
2182         $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2183     }
2185     if (empty($user->id)) {
2186         return false; // not logged in, can not be guest
2187     }
2189     return ($user->username == 'guest');
2192 /**
2193  * Determines if the currently logged in user is in editing mode
2194  *
2195  * @uses $USER
2196  * @param int $courseid The id of the course being tested
2197  * @param user $user A {@link $USER} object. If null then the currently logged in user is used.
2198  * @return bool
2199  */
2200 function isediting($courseid, $user=NULL) {
2201     global $USER;
2202     if (!$user) {
2203         $user = $USER;
2204     }
2205     if (empty($user->editing)) {
2206         return false;
2207     }
2208     return ($user->editing and has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $courseid)));
2211 /**
2212  * Determines if the logged in user is currently moving an activity
2213  *
2214  * @uses $USER
2215  * @param int $courseid The id of the course being tested
2216  * @return bool
2217  */
2218 function ismoving($courseid) {
2219     global $USER;
2221     if (!empty($USER->activitycopy)) {
2222         return ($USER->activitycopycourse == $courseid);
2223     }
2224     return false;
2227 /**
2228  * Given an object containing firstname and lastname
2229  * values, this function returns a string with the
2230  * full name of the person.
2231  * The result may depend on system settings
2232  * or language.  'override' will force both names
2233  * to be used even if system settings specify one.
2234  *
2235  * @uses $CFG
2236  * @uses $SESSION
2237  * @param object $user A {@link $USER} object to get full name of
2238  * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2239  */
2240 function fullname($user, $override=false) {
2242     global $CFG, $SESSION;
2244     if (!isset($user->firstname) and !isset($user->lastname)) {
2245         return '';
2246     }
2248     if (!$override) {
2249         if (!empty($CFG->forcefirstname)) {
2250             $user->firstname = $CFG->forcefirstname;
2251         }
2252         if (!empty($CFG->forcelastname)) {
2253             $user->lastname = $CFG->forcelastname;
2254         }
2255     }
2257     if (!empty($SESSION->fullnamedisplay)) {
2258         $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2259     }
2261     if ($CFG->fullnamedisplay == 'firstname lastname') {
2262         return $user->firstname .' '. $user->lastname;
2264     } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2265         return $user->lastname .' '. $user->firstname;
2267     } else if ($CFG->fullnamedisplay == 'firstname') {
2268         if ($override) {
2269             return get_string('fullnamedisplay', '', $user);
2270         } else {
2271             return $user->firstname;
2272         }
2273     }
2275     return get_string('fullnamedisplay', '', $user);
2278 /**
2279  * Sets a moodle cookie with an encrypted string
2280  *
2281  * @uses $CFG
2282  * @uses DAYSECS
2283  * @uses HOURSECS
2284  * @param string $thing The string to encrypt and place in a cookie
2285  */
2286 function set_moodle_cookie($thing) {
2287     global $CFG;
2289     if ($thing == 'guest') {  // Ignore guest account
2290         return;
2291     }
2293     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2295     $days = 60;
2296     $seconds = DAYSECS*$days;
2298     setCookie($cookiename, '', time() - HOURSECS, '/');
2299     setCookie($cookiename, rc4encrypt($thing), time()+$seconds, '/');
2302 /**
2303  * Gets a moodle cookie with an encrypted string
2304  *
2305  * @uses $CFG
2306  * @return string
2307  */
2308 function get_moodle_cookie() {
2309     global $CFG;
2311     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2313     if (empty($_COOKIE[$cookiename])) {
2314         return '';
2315     } else {
2316         $thing = rc4decrypt($_COOKIE[$cookiename]);
2317         return ($thing == 'guest') ? '': $thing;  // Ignore guest account
2318     }
2321 /**
2322  * Returns whether a given authentication plugin exists.
2323  *
2324  * @uses $CFG
2325  * @param string $auth Form of authentication to check for. Defaults to the
2326  *        global setting in {@link $CFG}.
2327  * @return boolean Whether the plugin is available.
2328  */
2329 function exists_auth_plugin($auth='') {
2330     global $CFG;
2331     
2332     // use the global default if not specified
2333     if ($auth == '') {
2334         $auth = $CFG->auth;
2335     }
2336     if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2337         return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2338     }
2339     return false;
2342 /**
2343  * Checks if a given plugin is in the list of enabled authentication plugins.
2344  * 
2345  * @param string $auth Authentication plugin.
2346  * @return boolean Whether the plugin is enabled.
2347  */
2348 function is_enabled_auth($auth='') {
2349     global $CFG;
2351     // use the global default if not specified
2352     if ($auth == '') {
2353         $auth = $CFG->auth;
2354     }
2355     return in_array($auth, explode(',', $CFG->auth_plugins_enabled));
2358 /**
2359  * Returns an authentication plugin instance.
2360  *
2361  * @uses $CFG
2362  * @param string $auth Form of authentication required. Defaults to the
2363  *        global setting in {@link $CFG}.
2364  * @return object An instance of the required authentication plugin.
2365  */
2366 function get_auth_plugin($auth = '') {
2367     global $CFG;
2368     
2369     // use the global default if not specified
2370     if ($auth == '') {
2371         $auth = $CFG->auth;
2372     }
2374     // TODO: plugin enabled?
2375     
2376     // check the plugin exists first
2377     if (! exists_auth_plugin($auth)) {
2378         error("Authentication plugin '$auth' not found.");
2379     }
2380     
2381     // return auth plugin instance
2382     require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2383     $class = "auth_plugin_$auth";
2384     return new $class;
2387 /**
2388  * Returns true if an internal authentication method is being used.
2389  * if method not specified then, global default is assumed
2390  *
2391  * @uses $CFG
2392  * @param string $auth Form of authentication required
2393  * @return bool
2394  * @todo Outline auth types and provide code example
2395  */
2396 function is_internal_auth($auth='') {
2397     $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2398     return $authplugin->is_internal();
2401 /**
2402  * Returns an array of user fields
2403  *
2404  * @uses $CFG
2405  * @uses $db
2406  * @return array User field/column names
2407  */
2408 function get_user_fieldnames() {
2410     global $CFG, $db;
2412     $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2413     unset($fieldarray['ID']);
2415     return $fieldarray;
2418 /**
2419  * Creates a bare-bones user record
2420  *
2421  * @uses $CFG
2422  * @param string $username New user's username to add to record
2423  * @param string $password New user's password to add to record
2424  * @param string $auth Form of authentication required
2425  * @return object A {@link $USER} object
2426  * @todo Outline auth types and provide code example
2427  */
2428 function create_user_record($username, $password, $auth='') {
2429     global $CFG;
2431     //just in case check text case
2432     $username = trim(moodle_strtolower($username));
2434     $authplugin = get_auth_plugin($auth);
2436     if (method_exists($authplugin, 'get_userinfo')) {
2437         if ($newinfo = $authplugin->get_userinfo($username)) {
2438             $newinfo = truncate_userinfo($newinfo);
2439             foreach ($newinfo as $key => $value){
2440                 $newuser->$key = addslashes(stripslashes($value)); // Just in case
2441             }
2442         }
2443     }
2445     if (!empty($newuser->email)) {
2446         if (email_is_not_allowed($newuser->email)) {
2447             unset($newuser->email);
2448         }
2449     }
2451     $newuser->auth = (empty($auth)) ? $CFG->auth : $auth;
2452     $newuser->username = $username;
2453     update_internal_user_password($newuser, $password, false);
2454     $newuser->lang = $CFG->lang;
2455     $newuser->confirmed = 1;
2456     $newuser->lastip = getremoteaddr();
2457     $newuser->timemodified = time();
2458     $newuser->mnethostid = $CFG->mnet_localhost_id;
2460     if (insert_record('user', $newuser)) {
2461          $user = get_complete_user_data('username', $newuser->username);
2462          if($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'}){
2463              set_user_preference('auth_forcepasswordchange', 1, $user->id);
2464          }
2465          return $user;
2466     }
2467     return false;
2470 /**
2471  * Will update a local user record from an external source
2472  *
2473  * @uses $CFG
2474  * @param string $username New user's username to add to record
2475  * @return user A {@link $USER} object
2476  */
2477 function update_user_record($username, $authplugin) {
2478     if (method_exists($authplugin, 'get_userinfo')) {
2479         $username = trim(moodle_strtolower($username)); /// just in case check text case
2481         $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2482         $userauth = get_auth_plugin($oldinfo->auth);
2484         if ($newinfo = $authplugin->get_userinfo($username)) {
2485             $newinfo = truncate_userinfo($newinfo);
2486             foreach ($newinfo as $key => $value){
2487                 $confkey = 'field_updatelocal_' . $key;
2488                 if (!empty($userauth->config->$confkey) and $userauth->config->$confkey === 'onlogin') {
2489                     $value = addslashes(stripslashes($value));   // Just in case
2490                     set_field('user', $key, $value, 'username', $username)
2491                         or error_log("Error updating $key for $username");
2492                 }
2493             }
2494         }
2495     }
2496     return get_complete_user_data('username', $username);
2499 function truncate_userinfo($info) {
2500 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2501 /// which may have large fields
2503     // define the limits
2504     $limit = array(
2505                     'username'    => 100,
2506                     'idnumber'    =>  64,
2507                     'firstname'   => 100,
2508                     'lastname'    => 100,
2509                     'email'       => 100,
2510                     'icq'         =>  15,
2511                     'phone1'      =>  20,
2512                     'phone2'      =>  20,
2513                     'institution' =>  40,
2514                     'department'  =>  30,
2515                     'address'     =>  70,
2516                     'city'        =>  20,
2517                     'country'     =>   2,
2518                     'url'         => 255,
2519                     );
2521     // apply where needed
2522     foreach (array_keys($info) as $key) {
2523         if (!empty($limit[$key])) {
2524             $info[$key] = trim(substr($info[$key],0, $limit[$key]));
2525         }
2526     }
2528     return $info;
2531 /**
2532  * Retrieve the guest user object
2533  *
2534  * @uses $CFG
2535  * @return user A {@link $USER} object
2536  */
2537 function guest_user() {
2538     global $CFG;
2540     if ($newuser = get_record('user', 'username', 'guest')) {
2541         $newuser->confirmed = 1;
2542         $newuser->lang = $CFG->lang;
2543         $newuser->lastip = getremoteaddr();
2544     }
2546     return $newuser;
2549 /**
2550  * Given a username and password, this function looks them
2551  * up using the currently selected authentication mechanism,
2552  * and if the authentication is successful, it returns a
2553  * valid $user object from the 'user' table.
2554  *
2555  * Uses auth_ functions from the currently active auth module
2556  *
2557  * @uses $CFG
2558  * @param string $username  User's username
2559  * @param string $password  User's password
2560  * @return user|flase A {@link $USER} object or false if error
2561  */
2562 function authenticate_user_login($username, $password) {
2564     global $CFG;
2566     // default to manual if global auth is undefined or broken
2567     if (empty($CFG->auth_plugins_enabled)) {
2568         $CFG->auth_plugins_enabled = empty($CFG->auth) ? 'manual' : $CFG->auth;
2569     }
2570     // if blank, set default auth to first enabled auth plugin
2571     if (empty($CFG->auth)) {
2572         $auths = explode(',', $CFG->auth_plugins_enabled);
2573         $CFG->auth = $auths[0];
2574     }
2576     // if user not found, use site auth
2577     if (!$user = get_complete_user_data('username', $username)) {
2578         $user = new object();
2579         $user->id = 0;     // Not a user
2580         $auth = $CFG->auth_plugins_enabled;
2581     }
2583     // Sort out the authentication method we are using.
2584     if (empty($user->auth)) {      // For some reason it isn't set yet
2585         $primadmin = get_admin();
2586         if (!empty($user->id) && (($user->id==$primadmin->id) || isguest($user->id))) {
2587             $auth = 'manual';    // always assume these guys are internal
2588         }
2589         else {
2590             $auth = $CFG->auth_plugins_enabled; // default to site method
2591         }
2592     } else {
2593         $auth = $user->auth;
2594     }
2596     // walk each authentication plugin, in order
2597     $auths = explode(',', $auth);
2598     foreach ($auths as $auth) {
2599         $authplugin = get_auth_plugin($auth);
2601         // on auth fail, log and fall through to the next plugin
2602         if (!$authplugin->user_login($username, $password)) {
2603             add_to_log(0, 'login', 'error', 'index.php', $username);
2604             error_log("[client {$_SERVER['REMOTE_ADDR']}]  $CFG->wwwroot  Auth=$auth  Failed Login:  $username  {$_SERVER['HTTP_USER_AGENT']}");
2605             continue;
2606         }
2608         // successful authentication
2609         if ($user->id) {                          // User already exists in database
2610             if (empty($user->auth)) {             // For some reason auth isn't set yet
2611                 set_field('user', 'auth', $auth, 'username', $username);
2612             }
2613             update_internal_user_password($user, $password);
2614             if (!$authplugin->is_internal()) {            // update user record from external DB
2615                 $user = update_user_record($username, get_auth_plugin($user->auth));
2616             }
2617         } else {
2618             $user = create_user_record($username, $password, $auth);
2619         }
2620         // fix for MDL-6928
2621         if (method_exists($authplugin, 'iscreator')) {
2622             $sitecontext = get_context_instance(CONTEXT_SYSTEM);
2623             if ($creatorroles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
2624                 $creatorrole = array_shift($creatorroles); // We can only use one, let's use the first one
2625                 // Check if the user is a creator
2626                 if ($authplugin->iscreator($username)) { // Following calls will not create duplicates
2627                     role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, $auth);
2628                 } else {
2629                     role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id);
2630                 }
2631             }
2632         }
2634     /// Log in to a second system if necessary
2635         if (!empty($CFG->sso)) {
2636             include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
2637             if (function_exists('sso_user_login')) {
2638                 if (!sso_user_login($username, $password)) {   // Perform the signon process
2639                     notify('Second sign-on failed');
2640                 }
2641             }
2642         }
2644         return $user;
2646     } 
2647     
2648     // failed if all the plugins have failed
2649     add_to_log(0, 'login', 'error', 'index.php', $username);
2650     error_log('[client '.$_SERVER['REMOTE_ADDR']."]  $CFG->wwwroot  Failed Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
2651     return false;
2654 /**
2655  * Compare password against hash stored in internal user table.
2656  * If necessary it also updates the stored hash to new format.
2657  * 
2658  * @param object user
2659  * @param string plain text password
2660  * @return bool is password valid?
2661  */
2662 function validate_internal_user_password(&$user, $password) {
2663     global $CFG;
2665     if (!isset($CFG->passwordsaltmain)) {
2666         $CFG->passwordsaltmain = '';
2667     }
2669     $validated = false;
2671         // get password original encoding in case it was not updated to unicode yet
2672     $textlib = textlib_get_instance();
2673     $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
2675     if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
2676         or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
2677         $validated = true;
2678     } else {
2679         for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
2680             $alt = 'passwordsaltalt'.$i;
2681             if (!empty($CFG->$alt)) {
2682                 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
2683                     $validated = true;
2684                     break;
2685                 }
2686             }
2687         }
2688     }
2690     if ($validated) {
2691         // force update of password hash using latest main password salt and encoding if needed
2692         update_internal_user_password($user, $password);
2693     }
2695     return $validated;
2698 /**
2699  * Calculate hashed value from password using current hash mechanism.
2700  * 
2701  * @param string password
2702  * @return string password hash
2703  */
2704 function hash_internal_user_password($password) {
2705     global $CFG;
2707     if (isset($CFG->passwordsaltmain)) {
2708         return md5($password.$CFG->passwordsaltmain);
2709     } else {
2710         return md5($password);
2711     }
2714 /**
2715  * Update pssword hash in user object.
2716  * 
2717  * @param object user
2718  * @param string plain text password
2719  * @param bool store changes also in db, default true
2720  * @return true if hash changed
2721  */
2722 function update_internal_user_password(&$user, $password, $storeindb=true) {
2723     global $CFG;
2725     $authplugin = get_auth_plugin($user->auth);
2726     if (!empty($authplugin->config->preventpassindb) /*|| $storeindb === false */) {
2727         $hashedpassword = 'not cached';
2728     } else {
2729         $hashedpassword = hash_internal_user_password($password);
2730     }
2732     return set_field('user', 'password',  $hashedpassword, 'id', $user->id);
2735 /**
2736  * Get a complete user record, which includes all the info
2737  * in the user record
2738  * Intended for setting as $USER session variable
2739  *
2740  * @uses $CFG
2741  * @uses SITEID
2742  * @param string $field The user field to be checked for a given value.
2743  * @param string $value The value to match for $field.
2744  * @return user A {@link $USER} object.
2745  */
2746 function get_complete_user_data($field, $value, $mnethostid=null) {
2748     global $CFG;
2750     if (!$field || !$value) {
2751         return false;
2752     }
2754 /// Build the WHERE clause for an SQL query
2756     $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
2758     if (null === $mnethostid) {
2759         $constraints .= ' AND auth != \'mnet\'';
2760     } elseif (is_numeric($mnethostid)) {
2761         $constraints .= ' AND mnethostid = \''.$mnethostid.'\'';
2762     } else {
2763         error_log('Call to get_complete_user_data for $field='.$field.', $value = '.$value.', with invalid $mnethostid: '. $mnethostid);
2764         print_error('invalidhostlogin','mnet', $CFG->wwwroot.'/login/index.php');
2765         exit;
2766     }
2768 /// Get all the basic user data
2770     if (! $user = get_record_select('user', $constraints)) {
2771         return false;
2772     }
2774 /// Get various settings and preferences
2776     if ($displays = get_records('course_display', 'userid', $user->id)) {
2777         foreach ($displays as $display) {
2778             $user->display[$display->course] = $display->display;
2779         }
2780     }
2782     if ($preferences = get_records('user_preferences', 'userid', $user->id)) {
2783         foreach ($preferences as $preference) {
2784             $user->preference[$preference->name] = $preference->value;
2785         }
2786     }
2788     if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
2789         foreach ($lastaccesses as $lastaccess) {
2790             $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
2791         }
2792     }
2794     if ($groupids = groups_get_all_groups_for_user($user->id)) { //TODO:check.
2795         foreach ($groupids as $groupid) {
2796             $courseid = groups_get_course($groupid);
2797             //change this to 2D array so we can put multiple groups in a course
2798             $user->groupmember[$courseid][] = $groupid;
2799         }
2800     }
2802 /// Rewrite some variables if necessary
2803     if (!empty($user->description)) {
2804         $user->description = true;   // No need to cart all of it around
2805     }
2806     if ($user->username == 'guest') {
2807         $user->lang       = $CFG->lang;               // Guest language always same as site
2808         $user->firstname  = get_string('guestuser');  // Name always in current language
2809         $user->lastname   = ' ';
2810     }
2812     $user->sesskey  = random_string(10);
2813     $user->sessionIP = md5(getremoteaddr());   // Store the current IP in the session
2815     return $user;
2820 /*
2821  * When logging in, this function is run to set certain preferences
2822  * for the current SESSION
2823  */
2824 function set_login_session_preferences() {
2825     global $SESSION, $CFG;
2827     $SESSION->justloggedin = true;
2829     unset($SESSION->lang);
2831     // Restore the calendar filters, if saved
2832     if (intval(get_user_preferences('calendar_persistflt', 0))) {
2833         include_once($CFG->dirroot.'/calendar/lib.php');
2834         calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
2835     }
2839 /**
2840  * Delete a course, including all related data from the database,
2841  * and any associated files from the moodledata folder.
2842  *
2843  * @param int $courseid The id of the course to delete.
2844  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2845  * @return bool true if all the removals succeeded. false if there were any failures. If this
2846  *             method returns false, some of the removals will probably have succeeded, and others
2847  *             failed, but you have no way of knowing which.
2848  */
2849 function delete_course($courseid, $showfeedback = true) {
2850     global $CFG;
2851     $result = true;
2853     if (!remove_course_contents($courseid, $showfeedback)) {
2854         if ($showfeedback) {
2855             notify("An error occurred while deleting some of the course contents.");
2856         }
2857         $result = false;
2858     }
2860     if (!delete_records("course", "id", $courseid)) {
2861         if ($showfeedback) {
2862             notify("An error occurred while deleting the main course record.");
2863         }
2864         $result = false;
2865     }
2867     if (!delete_records('context', 'contextlevel', CONTEXT_COURSE, 'instanceid', $courseid)) {
2868         if ($showfeedback) {
2869             notify("An error occurred while deleting the main context record.");
2870         }
2871         $result = false;
2872     }
2874     if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
2875         if ($showfeedback) {
2876             notify("An error occurred while deleting the course files.");
2877         }
2878         $result = false;
2879     }
2881     return $result;
2884 /**
2885  * Clear a course out completely, deleting all content
2886  * but don't delete the course itself
2887  *
2888  * @uses $CFG
2889  * @param int $courseid The id of the course that is being deleted
2890  * @param bool $showfeedback Whether to display notifications of each action the function performs.
2891  * @return bool true if all the removals succeeded. false if there were any failures. If this
2892  *             method returns false, some of the removals will probably have succeeded, and others
2893  *             failed, but you have no way of knowing which.
2894  */
2895 function remove_course_contents($courseid, $showfeedback=true) {
2897     global $CFG;
2899     $result = true;
2901     if (! $course = get_record('course', 'id', $courseid)) {
2902         error('Course ID was incorrect (can\'t find it)');
2903     }
2905     $strdeleted = get_string('deleted');
2907 /// First delete every instance of every module
2909     if ($allmods = get_records('modules') ) {
2910         foreach ($allmods as $mod) {
2911             $modname = $mod->name;
2912             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
2913             $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance
2914             $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon)
2915             $count=0;
2916             if (file_exists($modfile)) {
2917                 include_once($modfile);
2918                 if (function_exists($moddelete)) {
2919                     if ($instances = get_records($modname, 'course', $course->id)) {
2920                         foreach ($instances as $instance) {
2921                             if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
2922                                 delete_context(CONTEXT_MODULE, $cm->id);
2923                             }
2924                             if ($moddelete($instance->id)) {
2925                                 $count++;
2927                             } else {
2928                                 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
2929                                 $result = false;
2930                             }
2931                         }
2932                     }
2933                 } else {
2934                     notify('Function '. $moddelete() .'doesn\'t exist!');
2935                     $result = false;
2936                 }
2938                 if (function_exists($moddeletecourse)) {
2939                     $moddeletecourse($course, $showfeedback);
2940                 }
2941             }
2942             if ($showfeedback) {
2943                 notify($strdeleted .' '. $count .' x '. $modname);
2944             }
2945         }
2946     } else {
2947         error('No modules are installed!');
2948     }
2950 /// Give local code a chance to delete its references to this course.
2951     require_once('locallib.php');
2952     notify_local_delete_course($courseid, $showfeedback);
2954 /// Delete course blocks
2956     if ($blocks = get_records_sql("SELECT * 
2957                                    FROM {$CFG->prefix}block_instance
2958                                    WHERE pagetype = '".PAGE_COURSE_VIEW."'
2959                                    AND pageid = $course->id")) {
2960         if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
2961             if ($showfeedback) {
2962                 notify($strdeleted .' block_instance');
2963             }
2964             foreach ($blocks as $block) {  /// Delete any associated contexts for this block
2965                 delete_context(CONTEXT_BLOCK, $block->id);
2966             }
2967         } else {
2968             $result = false;
2969         }
2970     }
2972 /// Delete any groups, removing members and grouping/course links first.
2973     //TODO: If groups or groupings are to be shared between courses, think again!
2974     if ($groupids = groups_get_groups($course->id)) {
2975         foreach ($groupids as $groupid) {
2976             if (groups_remove_all_members($groupid)) {
2977                 if ($showfeedback) {
2978                     notify($strdeleted .' groups_members');
2979                 }
2980             } else {
2981                 $result = false;
2982             }
2983             /// Delete any associated context for this group ??
2984             delete_context(CONTEXT_GROUP, $groupid);
2985             
2986             if (groups_delete_group($groupid)) {
2987                 if ($showfeedback) {
2988                     notify($strdeleted .' groups');
2989                 }
2990             } else {
2991                 $result = false;
2992             }
2993         }
2994     }
2995 /// Delete any groupings.
2996     $result = groups_delete_all_groupings($course->id);
2997     if ($result && $showfeedback) {
2998         notify($strdeleted .' groupings');
2999     }
3001 /// Delete all related records in other tables that may have a courseid
3002 /// This array stores the tables that need to be cleared, as
3003 /// table_name => column_name that contains the course id.
3005     $tablestoclear = array(
3006         'event' => 'courseid', // Delete events
3007         'log' => 'course', // Delete logs
3008         'course_sections' => 'course', // Delete any course stuff
3009         'course_modules' => 'course',
3010         'grade_category' => 'courseid', // Delete gradebook stuff
3011         'grade_exceptions' => 'courseid',
3012         'grade_item' => 'courseid',
3013         'grade_letter' => 'courseid',
3014         'grade_preferences' => 'courseid'
3015     );
3016     foreach ($tablestoclear as $table => $col) {
3017         if (delete_records($table, $col, $course->id)) {
3018             if ($showfeedback) {
3019                 notify($strdeleted . ' ' . $table);
3020             }
3021         } else {
3022             $result = false;
3023         }
3024     }
3027 /// Clean up metacourse stuff
3029     if ($course->metacourse) {
3030         delete_records("course_meta","parent_course",$course->id);
3031         sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3032         if ($showfeedback) {
3033             notify("$strdeleted course_meta");
3034         }
3035     } else {
3036         if ($parents = get_records("course_meta","child_course",$course->id)) {
3037             foreach ($parents as $parent) {
3038                 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3039             }
3040             if ($showfeedback) {
3041                 notify("$strdeleted course_meta");
3042             }
3043         }
3044     }
3046 /// Delete questions and question categories
3047     include_once($CFG->libdir.'/questionlib.php');
3048     question_delete_course($course, $showfeedback);
3050 /// Delete all roles and overiddes in the course context (but keep the course context)
3051     if ($courseid != SITEID) {
3052         delete_context(CONTEXT_COURSE, $course->id);
3053     }
3054     
3055     return $result;
3059 /**
3060  * This function will empty a course of USER data as much as
3061 /// possible. It will retain the activities and the structure
3062 /// of the course.
3063  *
3064  * @uses $USER
3065  * @uses $SESSION
3066  * @uses $CFG
3067  * @param object $data an object containing all the boolean settings and courseid
3068  * @param bool $showfeedback  if false then do it all silently
3069  * @return bool
3070  * @todo Finish documenting this function
3071  */
3072 function reset_course_userdata($data, $showfeedback=true) {
3074     global $CFG, $USER, $SESSION;
3076     $result = true;
3078     $strdeleted = get_string('deleted');
3080     // Look in every instance of every module for data to delete
3082     if ($allmods = get_records('modules') ) {
3083         foreach ($allmods as $mod) {
3084             $modname = $mod->name;
3085             $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3086             $moddeleteuserdata = $modname .'_delete_userdata';   // Function to delete user data
3087             if (file_exists($modfile)) {
3088                 @include_once($modfile);
3089                 if (function_exists($moddeleteuserdata)) {
3090                     $moddeleteuserdata($data, $showfeedback);
3091                 }
3092             }
3093         }
3094     } else {
3095         error('No modules are installed!');
3096     }
3098     // Delete other stuff
3099     $coursecontext = get_context_instance(CONTEXT_COURSE, $data->courseid);
3101     if (!empty($data->reset_students) or !empty($data->reset_teachers)) {
3102         $teachers     = array_keys(get_users_by_capability($coursecontext, 'moodle/course:update'));
3103         $participants = array_keys(get_users_by_capability($coursecontext, 'moodle/course:view'));
3104         $students     = array_diff($participants, $teachers);
3106         if (!empty($data->reset_students)) {
3107             foreach ($students as $studentid) {
3108                 role_unassign(0, $studentid, 0, $coursecontext->id);
3109             }
3110             if ($showfeedback) {
3111                 notify($strdeleted .' '.get_string('students'), 'notifysuccess');
3112             }
3114             /// Delete group members (but keep the groups) TODO:check.
3115             if ($groupids = groups_get_groups($data->courseid)) {
3116                 foreach ($groupids as $groupid) {
3117                     if (groups_remove_all_group_members($groupid)) {
3118                         if ($showfeedback) {
3119                             notify($strdeleted .' groups_members', 'notifysuccess');
3120                         }
3121                     } else {
3122                         $result = false;
3123                     }
3124                 }
3125             }
3126         }
3128         if (!empty($data->reset_teachers)) {
3129             foreach ($teachers as $teacherid) {
3130                 role_unassign(0, $teacherid, 0, $coursecontext->id);
3131             }
3132             if ($showfeedback) {
3133                 notify($strdeleted .' '.get_string('teachers'), 'notifysuccess');
3134             }
3135         }
3136     }
3138     if (!empty($data->reset_groups)) {
3139         if ($groupids = groups_get_groups($data->courseid)) {
3140             foreach ($groupids as $groupid) {
3141                 if (groups_delete_group($groupid)) {
3142                     if ($showfeedback) {
3143                         notify($strdeleted .' groups', 'notifysuccess');
3144                     }
3145                 } else {
3146                     $result = false;
3147                 }
3148             }
3149         }
3150     }
3152     if (!empty($data->reset_events)) {
3153         if (delete_records('event', 'courseid', $data->courseid)) {
3154             if ($showfeedback) {
3155                 notify($strdeleted .' event', 'notifysuccess');
3156             }
3157         } else {
3158             $result = false;
3159         }
3160     }
3162     if (!empty($data->reset_logs)) {
3163         if (delete_records('log', 'course', $data->courseid)) {
3164             if ($showfeedback) {
3165                 notify($strdeleted .' log', 'notifysuccess');
3166             }
3167         } else {
3168             $result = false;
3169         }
3170     }
3172     // deletes all role assignments, and local override, these have no courseid in table and needs separate process
3173     $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3174     delete_records('role_capabilities', 'contextid', $context->id);
3176     return $result;
3180 require_once($CFG->dirroot.'/group/lib.php');
3181 /*TODO: functions moved to /group/lib/legacylib.php
3183 ismember
3184 add_user_to_group
3185 mygroupid
3186 groupmode
3187 set_current_group
3188 ... */
3191 function generate_email_processing_address($modid,$modargs) {
3192     global $CFG;
3194     if (empty($CFG->siteidentifier)) {    // Unique site identification code
3195         set_config('siteidentifier', random_string(32));
3196     }
3198     $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3199     return $header . substr(md5($header.$CFG->siteidentifier),0,16).'@'.$CFG->maildomain;
3203 function moodle_process_email($modargs,$body) {
3204     // the first char should be an unencoded letter. We'll take this as an action
3205     switch ($modargs{0}) {
3206         case 'B': { // bounce
3207             list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3208             if ($user = get_record_select("user","id=$userid","id,email")) {
3209                 // check the half md5 of their email
3210                 $md5check = substr(md5($user->email),0,16);
3211                 if ($md5check == substr($modargs, -16)) {
3212                     set_bounce_count($user);
3213                 }
3214                 // else maybe they've already changed it?
3215             }
3216         }
3217         break;
3218         // maybe more later?
3219     }
3222 /// CORRESPONDENCE  ////////////////////////////////////////////////
3224 /**
3225  * Send an email to a specified user
3226  *
3227  * @uses $CFG
3228  * @uses $FULLME
3229  * @uses SITEID
3230  * @param user $user  A {@link $USER} object
3231  * @param user $from A {@link $USER} object
3232  * @param string $subject plain text subject line of the email
3233  * @param string $messagetext plain text version of the message
3234  * @param string $messagehtml complete html version of the message (optional)
3235  * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
3236  * @param string $attachname the name of the file (extension indicates MIME)
3237  * @param bool $usetrueaddress determines whether $from email address should
3238  *          be sent out. Will be overruled by user profile setting for maildisplay
3239  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3240  *          was blocked by user and "false" if there was another sort of error.
3241  */
3242 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='') {
3244     global $CFG, $FULLME;
3246     include_once($CFG->libdir .'/phpmailer/class.phpmailer.php');
3248 /// We are going to use textlib services here
3249     $textlib = textlib_get_instance();
3251     if (empty($user)) {
3252         return false;
3253     }
3255     if (!empty($user->emailstop)) {
3256         return 'emailstop';
3257     }
3259     if (over_bounce_threshold($user)) {
3260         error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
3261         return false;
3262     }
3264     $mail = new phpmailer;
3266     $mail->Version = 'Moodle '. $CFG->version;           // mailer version
3267     $mail->PluginDir = $CFG->libdir .'/phpmailer/';      // plugin directory (eg smtp plugin)
3269     $mail->CharSet = 'utf-8';
3271     if ($CFG->smtphosts == 'qmail') {
3272         $mail->IsQmail();                              // use Qmail system
3274     } else if (empty($CFG->smtphosts)) {
3275         $mail->IsMail();                               // use PHP mail() = sendmail
3277     } else {
3278         $mail->IsSMTP();                               // use SMTP directly
3279         if (debugging()) {
3280             echo '<pre>' . "\n";
3281             $mail->SMTPDebug = true;
3282         }
3283         $mail->Host = $CFG->smtphosts;               // specify main and backup servers
3285         if ($CFG->smtpuser) {                          // Use SMTP authentication
3286             $mail->SMTPAuth = true;
3287             $mail->Username = $CFG->smtpuser;
3288             $mail->Password = $CFG->smtppass;
3289         }
3290     }
3292     $adminuser = get_admin();
3294     // make up an email address for handling bounces
3295     if (!empty($CFG->handlebounces)) {
3296         $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
3297         $mail->Sender = generate_email_processing_address(0,$modargs);
3298     }
3299     else {
3300         $mail->Sender   = $adminuser->email;
3301     }
3303     if (is_string($from)) { // So we can pass whatever we want if there is need
3304         $mail->From     = $CFG->noreplyaddress;
3305         $mail->FromName = $from;
3306     } else if ($usetrueaddress and $from->maildisplay) {
3307         $mail->From     = $from->email;
3308         $mail->FromName = fullname($from);
3309     } else {
3310         $mail->From     = $CFG->noreplyaddress;
3311         $mail->FromName = fullname($from);
3312         if (empty($replyto)) {
3313             $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
3314         }
3315     }
3317     if (!empty($replyto)) {
3318         $mail->AddReplyTo($replyto,$replytoname);
3319     }
3321     $mail->Subject = substr(stripslashes($subject), 0, 900);
3323     $mail->AddAddress($user->email, fullname($user) );
3325     $mail->WordWrap = 79;                               // set word wrap
3327     if (!empty($from->customheaders)) {                 // Add custom headers
3328         if (is_array($from->customheaders)) {
3329             foreach ($from->customheaders as $customheader) {
3330                 $mail->AddCustomHeader($customheader);
3331             }
3332         } else {
3333             $mail->AddCustomHeader($from->customheaders);
3334         }
3335     }
3337     if (!empty($from->priority)) {
3338         $mail->Priority = $from->priority;
3339     }
3341     if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
3342         $mail->IsHTML(true);
3343         $mail->Encoding = 'quoted-printable';           // Encoding to use
3344         $mail->Body    =  $messagehtml;
3345         $mail->AltBody =  "\n$messagetext\n";
3346     } else {
3347         $mail->IsHTML(false);
3348         $mail->Body =  "\n$messagetext\n";
3349     }
3351     if ($attachment && $attachname) {
3352         if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
3353             $mail->AddAddress($adminuser->email, fullname($adminuser) );
3354             $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
3355         } else {
3356             require_once($CFG->libdir.'/filelib.php');
3357             $mimetype = mimeinfo('type', $attachname);
3358             $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
3359         }
3360     }
3364 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
3365 /// encoding to the specified one
3366     if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
3367     /// Set it to site mail charset
3368         $charset = $CFG->sitemailcharset;
3369     /// Overwrite it with the user mail charset
3370         if (!empty($CFG->allowusermailcharset)) {
3371             if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
3372                 $charset = $useremailcharset;
3373             }
3374         }
3375     /// If it has changed, convert all the necessary strings
3376         if ($mail->CharSet != $charset) {
3377         /// Save the new mail charset
3378             $mail->CharSet = $charset;
3379         /// And convert some strings
3380             $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
3381             foreach ($mail->ReplyTo as $key => $rt) {                                      //ReplyTo Names
3382                 $mail->ReplyTo[$key][1] = $textlib->convert($rt, 'utf-8', $mail->CharSet);
3383             }
3384             $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet);   //Subject
3385             foreach ($mail->to as $key => $to) {
3386                 $mail->to[$key][1] = $textlib->convert($to, 'utf-8', $mail->CharSet);      //To Names
3387             }
3388             $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet);         //Body
3389             $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet);   //Subject
3390         }
3391     }
3393     if ($mail->Send()) {
3394         set_send_count($user);
3395         return true;
3396     } else {
3397         mtrace('ERROR: '. $mail->ErrorInfo);
3398         add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
3399         return false;
3400     }
3403 /**
3404  * Sets specified user's password and send the new password to the user via email.
3405  *
3406  * @uses $CFG
3407  * @param user $user A {@link $USER} object
3408  * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
3409  *          was blocked by user and "false" if there was another sort of error.
3410  */
3411 function setnew_password_and_mail($user) {
3413     global $CFG;
3415     $site  = get_site();
3416     $from = get_admin();
3418     $newpassword = generate_password();
3420     if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
3421         trigger_error('Could not set user password!');
3422         return false;
3423     }
3425     $a = new object();
3426     $a->firstname   = $user->firstname;
3427     $a->sitename    = $site->fullname;
3428     $a->username    = $user->username;
3429     $a->newpassword = $newpassword;
3430     $a->link        = $CFG->wwwroot .'/login/';
3431     $a->signoff     = fullname($from, true).' ('. $from->email .')';
3433     $message = get_string('newusernewpasswordtext', '', $a);
3435     $subject  = $site->fullname .': '. get_string('newusernewpasswordsubj');
3437     return email_to_user($user, $from, $subject, $message);
3441 /**
3442  * Resets specified user's password and send the new password to the user via email.
3443  *
3444  * @uses $CFG
3445  * @param user $user A {@link $USER} object
3446  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3447  *          was blocked by user and "false" if there was another sort of error.
3448  */
3449 function reset_password_and_mail($user) {
3451     global $CFG;
3453     $site  = get_site();
3454     $from = get_admin();
3456     $external = false;
3457     
3458     $userauth = get_auth_plugin($user->auth);
3459     if (!$userauth->can_change_password()) {
3460         trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
3461         return false;
3462     }
3464     $newpassword = generate_password();
3466     if (!$userauth->user_update_password($user->username, $newpassword)) {
3467         error("Could not set user password!");
3468     }
3470     $a = new object();
3471     $a->firstname = $user->firstname;
3472     $a->sitename = $site->fullname;
3473     $a->username = $user->username;
3474     $a->newpassword = $newpassword;
3475     $a->link = $CFG->httpswwwroot .'/login/change_password.php';
3476     $a->signoff = fullname($from, true).' ('. $from->email .')';
3478     $message = get_string('newpasswordtext', '', $a);
3480     $subject  = $site->fullname .': '. get_string('changedpassword');
3482     return email_to_user($user, $from, $subject, $message);
3486 /**
3487  * Send email to specified user with confirmation text and activation link.
3488  *
3489  * @uses $CFG
3490  * @param user $user A {@link $USER} object
3491  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3492  *          was blocked by user and "false" if there was another sort of error.
3493  */
3494  function send_confirmation_email($user) {
3496     global $CFG;
3498     $site = get_site();
3499     $from = get_admin();
3501     $data = new object();
3502     $data->firstname = fullname($user);
3503     $data->sitename = $site->fullname;
3504     $data->admin = fullname($from) .' ('. $from->email .')';
3506     $subject = get_string('emailconfirmationsubject', '', $site->fullname);
3508     $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $user->username;
3509     $message     = get_string('emailconfirmation', '', $data);
3510     $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
3512     $user->mailformat = 1;  // Always send HTML version as well
3514     return email_to_user($user, $from, $subject, $message, $messagehtml);
3518 /**
3519  * send_password_change_confirmation_email.
3520  *
3521  * @uses $CFG
3522  * @param user $user A {@link $USER} object
3523  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
3524  *          was blocked by user and "false" if there was another sort of error.
3525  */
3526 function send_password_change_confirmation_email($user) {
3528     global $CFG;
3530     $site = get_site();
3531     $from = get_admin();
3533     $data = new object();
3534     $data->firstname = $user->firstname;
3535     $data->sitename = $site->fullname;
3536     $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. $user->username;
3537     $data->admin = fullname($from).' ('. $from->email .')';
3539     $message = get_string('emailpasswordconfirmation', '', $data);
3540     $subject = get_string('emailpasswordconfirmationsubject', '', $site->fullname);
3542     return email_to_user($user, $from, $subject, $message);
3546 /**
3547  * Check that an email is allowed.  It returns an error message if there
3548  * was a problem.
3549  *
3550  * @uses $CFG
3551  * @param  string $email Content of email
3552  * @return string|false
3553  */
3554 function email_is_not_allowed($email) {
3556     global $CFG;
3558     if (!empty($CFG->allowemailaddresses)) {
3559         $allowed = explode(' ', $CFG->allowemailaddresses);
3560         foreach ($allowed as $allowedpattern) {
3561             $allowedpattern = trim($allowedpattern);
3562             if (!$allowedpattern) {
3563                 continue;
3564             }
3565             if (strpos(strrev($email), strrev($allowedpattern)) === 0) { // Match!   (bug 5250)
3566                 return false;
3567             }
3568         }
3569         return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
3571     } else if (!empty($CFG->denyemailaddresses)) {
3572         $denied = explode(' ', $CFG->denyemailaddresses);
3573         foreach ($denied as $deniedpattern) {
3574             $deniedpattern = trim($deniedpattern);
3575             if (!$deniedpattern) {
3576                 continue;
3577             }
3578             if (strpos(strrev($email), strrev($deniedpattern)) === 0) { // Match!   (bug 5250)
3579                 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
3580             }
3581         }
3582     }
3584     return false;
3587 function email_welcome_message_to_user($course, $user=NULL) {
3588     global $CFG, $USER;
3590     if (empty($user)) {
3591         if (!isloggedin()) {
3592             return false;
3593         }
3594         $user = $USER;
3595     }
3597     if (!empty($course->welcomemessage)) {
3598         $subject = get_string('welcometocourse', '', $course->fullname);
3600         $a->coursename = $course->fullname;
3601         $a->profileurl = "$CFG->wwwroot/user/view.php?id=$USER->id&course=$course->id";
3602         //$message = get_string("welcometocoursetext", "", $a);
3603         $message = $course->welcomemessage;
3605         if (! $teacher = get_teacher($course->id)) {
3606             $teacher = get_admin();
3607         }
3608         email_to_user($user, $teacher, $subject, $message);
3609     }
3612 /// FILE HANDLING  /////////////////////////////////////////////
3615 /**
3616  * Makes an upload directory for a particular module.
3617  *
3618  * @uses $CFG
3619  * @param int $courseid The id of the course in question - maps to id field of 'course' table.
3620  * @return string|false Returns full path to directory if successful, false if not
3621  */
3622 function make_mod_upload_directory($courseid) {
3623     global $CFG;
3625     if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
3626         return false;
3627     }
3629     $strreadme = get_string('readme');
3631     if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
3632         copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3633     } else {
3634         copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
3635     }
3636     return $moddata;
3639 /**
3640  * Returns current name of file on disk if it exists.
3641  *
3642  * @param string $newfile File to be verified
3643  * @return string Current name of file on disk if true
3644  */
3645 function valid_uploaded_file($newfile) {
3646     if (empty($newfile)) {
3647         return '';
3648     }
3649     if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
3650         return $newfile['tmp_name'];
3651     } else {
3652         return '';
3653     }
3656 /**
3657  * Returns the maximum size for uploading files.
3658  *
3659  * There are seven possible upload limits:
3660  * 1. in Apache using LimitRequestBody (no way of checking or changing this)
3661  * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
3662  * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
3663  * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
3664  * 5. by the Moodle admin in $CFG->maxbytes
3665  * 6. by the teacher in the current course $course->maxbytes
3666  * 7. by the teacher for the current module, eg $assignment->maxbytes
3667  *
3668  * These last two are passed to this function as arguments (in bytes).
3669  * Anything defined as 0 is ignored.
3670  * The smallest of all the non-zero numbers is returned.
3671  *
3672  * @param int $sizebytes ?
3673  * @param int $coursebytes Current course $course->maxbytes (in bytes)
3674  * @param int $modulebytes Current module ->maxbytes (in bytes)
3675  * @return int The maximum size for uploading files.
3676  * @todo Finish documenting this function
3677  */
3678 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
3680     if (! $filesize = ini_get('upload_max_filesize')) {
3681         $filesize = '5M';
3682     }
3683     $minimumsize = get_real_size($filesize);
3685     if ($postsize = ini_get('post_max_size')) {
3686         $postsize = get_real_size($postsize);
3687         if ($postsize < $minimumsize) {
3688             $minimumsize = $postsize;
3689         }
3690     }
3692     if ($sitebytes and $sitebytes < $minimumsize) {
3693         $minimumsize = $sitebytes;
3694     }
3696     if ($coursebytes and $coursebytes < $minimumsize) {
3697         $minimumsize = $coursebytes;
3698     }
3700     if ($modulebytes and $modulebytes < $minimumsize) {
3701         $minimumsize = $modulebytes;
3702     }
3704     return $minimumsize;
3707 /**
3708  * Related to {@link get_max_upload_file_size()} - this function returns an
3709  * array of possible sizes in an array, translated to the
3710  * local language.
3711  *
3712  * @uses SORT_NUMERIC
3713  * @param int $sizebytes ?
3714  * @param int $coursebytes Current course $course->maxbytes (in bytes)
3715  * @param int $modulebytes Current module ->maxbytes (in bytes)
3716  * @return int
3717  * @todo Finish documenting this function
3718  */
3719 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
3721     if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
3722         return array();
3723     }
3725     $filesize[$maxsize] = display_size($maxsize);
3727     $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
3728                       5242880, 10485760, 20971520, 52428800, 104857600);
3730     foreach ($sizelist as $sizebytes) {
3731        if ($sizebytes < $maxsize) {
3732            $filesize[$sizebytes] = display_size($sizebytes);
3733        }
3734     }
3736     krsort($filesize, SORT_NUMERIC);
3738     return $filesize;
3741 /**
3742  * If there has been an error uploading a file, print the appropriate error message
3743  * Numerical constants used as constant definitions not added until PHP version 4.2.0
3744  *
3745  * $filearray is a 1-dimensional sub-array of the $_FILES array
3746  * eg $filearray = $_FILES['userfile1']
3747  * If left empty then the first element of the $_FILES array will be used
3748  *
3749  * @uses $_FILES
3750  * @param array $filearray  A 1-dimensional sub-array of the $_FILES array
3751  * @param bool $returnerror If true then a string error message will be returned. Otherwise the user will be notified of the error in a notify() call.
3752  * @return bool|string
3753  */
3754 function print_file_upload_error($filearray = '', $returnerror = false) {
3756     if ($filearray == '' or !isset($filearray['error'])) {
3758         if (empty($_FILES)) return false;
3760         $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
3761         $filearray = array_shift($files); /// use first element of array
3762     }
3764     switch ($filearray['error']) {
3766         case 0: // UPLOAD_ERR_OK
3767             if ($filearray['size'] > 0) {
3768                 $errmessage = get_string('uploadproblem', $filearray['name']);
3769             } else {
3770                 $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
3771             }
3772             break;
3774         case 1: // UPLOAD_ERR_INI_SIZE
3775             $errmessage = get_string('uploadserverlimit');
3776             break;
3778         case 2: // UPLOAD_ERR_FORM_SIZE
3779             $errmessage = get_string('uploadformlimit');
3780             break;
3782         case 3: // UPLOAD_ERR_PARTIAL
3783             $errmessage = get_string('uploadpartialfile');
3784             break;
3786         case 4: // UPLOAD_ERR_NO_FILE
3787             $errmessage = get_string('uploadnofilefound');
3788             break;
3790         default:
3791             $errmessage = get_string('uploadproblem', $filearray['name']);
3792     }
3794     if ($returnerror) {
3795         return $errmessage;
3796     } else {
3797         notify($errmessage);
3798         return true;
3799     }
3803 /**
3804  * handy function to loop through an array of files and resolve any filename conflicts
3805  * both in the array of filenames and for what is already on disk.
3806  * not really compatible with the similar function in uploadlib.php
3807  * but this could be used for files/index.php for moving files around.
3808  */
3810 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
3811     foreach ($files as $k => $f) {
3812         if (check_potential_filename($destination,$f,$files)) {
3813             $bits = explode('.', $f);
3814             for ($i = 1; true; $i++) {
3815                 $try = sprintf($format, $bits[0], $i, $bits[1]);
3816                 if (!check_potential_filename($destination,$try,$files)) {
3817                     $files[$k] = $try;
3818                     break;
3819                 }
3820             }
3821         }
3822     }
3823     return $files;
3826 /**
3827  * @used by resolve_filename_collisions
3828  */
3829 function check_potential_filename($destination,$filename,$files) {
3830     if (file_exists($destination.'/'.$filename)) {
3831         return true;
3832     }
3833     if (count(array_keys($files,$filename)) > 1) {
3834         return true;
3835     }
3836     return false;
3840 /**
3841  * Returns an array with all the filenames in
3842  * all subdirectories, relative to the given rootdir.
3843  * If excludefile is defined, then that file/directory is ignored
3844  * If getdirs is true, then (sub)directories are included in the output
3845  * If getfiles is true, then files are included in the output
3846  * (at least one of these must be true!)
3847  *
3848  * @param string $rootdir  ?
3849  * @param string $excludefile  If defined then the specified file/directory is ignored
3850  * @param bool $descend  ?
3851  * @param bool $getdirs  If true then (sub)directories are included in the output
3852  * @param bool $getfiles  If true then files are included in the output
3853  * @return array An array with all the filenames in
3854  * all subdirectories, relative to the given rootdir
3855  * @todo Finish documenting this function. Add examples of $excludefile usage.
3856  */
3857 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
3859     $dirs = array();
3861     if (!$getdirs and !$getfiles) {   // Nothing to show
3862         return $dirs;
3863     }
3865     if (!is_dir($rootdir)) {          // Must be a directory
3866         return $dirs;
3867     }
3869     if (!$dir = opendir($rootdir)) {  // Can't open it for some reason
3870         return $dirs;
3871     }
3873     if (!is_array($excludefiles)) {
3874         $excludefiles = array($excludefiles);
3875     }
3877     while (false !== ($file = readdir($dir))) {
3878         $firstchar = substr($file, 0, 1);
3879         if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
3880             continue;
3881         }
3882         $fullfile = $rootdir .'/'. $file;
3883         if (filetype($fullfile) == 'dir') {
3884             if ($getdirs) {
3885                 $dirs[] = $file;
3886             }
3887             if ($descend) {
3888                 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
3889                 foreach ($subdirs as $subdir) {
3890                     $dirs[] = $file .'/'. $subdir;
3891                 }
3892             }
3893         } else if ($getfiles) {
3894             $dirs[] = $file;
3895         }
3896     }
3897     closedir($dir);
3899     asort($dirs);
3901     return $dirs;
3905 /**
3906  * Adds up all the files in a directory and works out the size.
3907  *
3908  * @param string $rootdir  ?
3909  * @param string $excludefile  ?
3910  * @return array
3911  * @todo Finish documenting this function
3912  */
3913 function get_directory_size($rootdir, $excludefile='') {
3915     global $CFG;
3917     // do it this way if we can, it's much faster
3918     if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
3919         $command = trim($CFG->pathtodu).' -sk --apparent-size '.escapeshellarg($rootdir);
3920         $output = null;
3921         $return = null;
3922         exec($command,$output,$return);
3923         if (is_array($output)) {
3924             return get_real_size(intval($output[0]).'k'); // we told it to return k.
3925         }
3926     }
3928     if (!is_dir($rootdir)) {          // Must be a directory
3929         return 0;
3930     }
3932     if (!$dir = @opendir($rootdir)) {  // Can't open it for some reason
3933         return 0;
3934     }
3936     $size = 0;
3938     while (false !== ($file = readdir($dir))) {
3939         $firstchar = substr($file, 0, 1);
3940         if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
3941             continue;
3942         }
3943         $fullfile = $rootdir .'/'. $file;
3944         if (filetype($fullfile) == 'dir') {
3945             $size += get_directory_size($fullfile, $excludefile);
3946         } else {
3947             $size += filesize($fullfile);
3948         }
3949     }
3950     closedir($dir);
3952     return $size;
3955 /**
3956  * Converts bytes into display form
3957  *
3958  * @param string $size  ?
3959  * @return string
3960  * @staticvar string $gb Localized string for size in gigabytes
3961  * @staticvar string $mb Localized string for size in megabytes
3962  * @staticvar string $kb Localized string for size in kilobytes
3963  * @staticvar string $b Localized string for size in bytes
3964  * @todo Finish documenting this function. Verify return type.
3965  */
3966 function display_size($size) {
3968     static $gb, $mb, $kb, $b;
3970     if (empty($gb)) {
3971         $gb = get_string('sizegb');
3972         $mb = get_string('sizemb');
3973         $kb = get_string('sizekb');
3974         $b  = get_string('sizeb');
3975     }
3977     if ($size >= 1073741824) {
3978         $size = round($size / 1073741824 * 10) / 10 . $gb;
3979     } else if ($size >= 1048576) {
3980         $size = round($size / 1048576 * 10) / 10 . $mb;
3981     } else if ($size >= 1024) {
3982         $size = round($size / 1024 * 10) / 10 . $kb;
3983     } else {
3984         $size = $size .' '. $b;
3985     }
3986     return $size;
3989 /**
3990  * Cleans a given filename by removing suspicious or troublesome characters
3991  * Only these are allowed: alphanumeric _ - .
3992  * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
3993  *
3994  * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
3995  *          because native zip binaries do weird character conversions. Use PHP zipping instead.
3996  *
3997  * @param string $string  file name
3998  * @return string cleaned file name
3999  */
4000 function clean_filename($string) {
4001     global $CFG;
4002     if (empty($CFG->unicodecleanfilename)) {
4003         $textlib = textlib_get_instance();
4004         $string = $textlib->specialtoascii($string);
4005         $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
4006     } else {
4007         //clean only ascii range
4008         $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
4009     }
4010     $string = preg_replace("/_+/", '_', $string);
4011     $string = preg_replace("/\.\.+/", '.', $string);
4012     return $string;
4016 /// STRING TRANSLATION  ////////////////////////////////////////
4018 /**
4019  * Returns the code for the current language
4020  *
4021  * @uses $CFG
4022  * @param $USER
4023  * @param $SESSION
4024  * @return string
4025  */
4026 function current_language() {
4027     global $CFG, $USER, $SESSION;
4029     if (!empty($CFG->courselang)) {    // Course language can override all other settings for this page
4030         $return = $CFG->courselang;
4032     } else if (!empty($SESSION->lang)) {    // Session language can override other settings
4033         $return = $SESSION->lang;
4035     } else if (!empty($USER->lang)) {    // User language can override site language
4036         $return = $USER->lang;
4038     } else {
4039         $return = $CFG->lang;
4040     }
4042     if ($return == 'en') {
4043         $return = 'en_utf8';
4044     }
4046     return $return;
4049 /* Obsoleted function - returns the code of the current charset - originally depended on the selected language pack.
4050  *
4051  * @param $ignorecache not used anymore
4052  * @return string always returns 'UTF-8'
4053  */
4054 function current_charset($ignorecache = false) {
4055     return 'UTF-8';
4058 /**
4059  * Prints out a translated string.
4060  *
4061  * Prints out a translated string using the return value from the {@link get_string()} function.
4062  *
4063  * Example usage of this function when the string is in the moodle.php file:<br/>
4064  * <code>
4065  * echo '<strong>';
4066  * print_string('wordforstudent');
4067  * echo '</strong>';
4068  * </code>
4069  *
4070  * Example usage of this function when the string is not in the moodle.php file:<br/>
4071  * <code>
4072  * echo '<h1>';
4073  * print_string('typecourse', 'calendar');
4074  * echo '</h1>';
4075  * </code>
4076  *
4077  * @param string $identifier The key identifier for the localized string
4078  * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
4079  * @param mixed $a An object, string or number that can be used
4080  * within translation strings
4081  */
4082 function print_string($identifier, $module='', $a=NULL) {
4083     echo get_string($identifier, $module, $a);
4086 /**
4087  * fix up the optional data in get_string()/print_string() etc
4088  * ensure possible sprintf() format characters are escaped correctly
4089  * needs to handle arbitrary strings and objects
4090  * @param mixed $a An object, string or number that can be used
4091  * @return mixed the supplied parameter 'cleaned'
4092  */
4093 function clean_getstring_data( $a ) {
4094     if (is_string($a)) {
4095         return str_replace( '%','%%',$a );
4096     }
4097     elseif (is_object($a)) {
4098         $a_vars = get_object_vars( $a );
4099         $new_a_vars = array();
4100         foreach ($a_vars as $fname => $a_var) {
4101             $new_a_vars[$fname] = clean_getstring_data( $a_var );
4102         }
4103         return (object)$new_a_vars;
4104     }
4105     else {
4106         return $a;
4107     }
4110 /**
4111  * Returns a localized string.
4112  *
4113  * Returns the translated string specified by $identifier as
4114  * for $module.  Uses the same format files as STphp.
4115  * $a is an object, string or number that can be used
4116  * within translation strings
4117  *
4118  * eg "hello \$a->firstname \$a->lastname"
4119  * or "hello \$a"
4120  *
4121  * If you would like to directly echo the localized string use
4122  * the function {@link print_string()}
4123  *
4124  * Example usage of this function involves finding the string you would
4125  * like a local equivalent of and using its identifier and module information
4126  * to retrive it.<br/>
4127  * If you open moodle/lang/en/moodle.php and look near line 1031
4128  * you will find a string to prompt a user for their word for student
4129  * <code>
4130  * $string['wordforstudent'] = 'Your word for Student';
4131  * </code>
4132  * So if you want to display the string 'Your word for student'
4133  * in any language that supports it on your site
4134  * you just need to use the identifier 'wordforstudent'
4135  * <code>
4136  * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
4137 or
4138  * </code>
4139  * If the string you want is in another file you'd take a slightly
4140  * different approach. Looking in moodle/lang/en/calendar.php you find
4141  * around line 75:
4142  * <code>
4143  * $string['typecourse'] = 'Course event';
4144  * </code>
4145  * If you want to display the string "Course event" in any language
4146  * supported you would use the identifier 'typecourse' and the module 'calendar'
4147  * (because it is in the file calendar.php):
4148  * <code>
4149  * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
4150  * </code>
4151  *
4152  * As a last resort, should the identifier fail to map to a string
4153  * the returned string will be [[ $identifier ]]
4154  *
4155  * @uses $CFG
4156  * @param string $identifier The key identifier for the localized string
4157  * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
4158  * @param mixed $a An object, string or number that can be used
4159  * within translation strings
4160  * @param array $extralocations An array of strings with other locations to look for string files
4161  * @return string The localized string.
4162  */
4163 function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
4165     global $CFG;
4167 /// originally these special strings were stored in moodle.php now we are only in langconfig.php
4168     $langconfigstrs = array('alphabet', 'backupnameformat', 'firstdayofweek', 'locale', 
4169                             'localewin', 'localewincharset', 'oldcharset',
4170                             'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
4171                             'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
4172                             'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
4173                             'thischarset', 'thisdirection', 'thislanguage');
4175     $filetocheck = 'langconfig.php';
4176     $defaultlang = 'en_utf8';
4177     if (in_array($identifier, $langconfigstrs)) {
4178         $module = 'langconfig';  //This strings are under langconfig.php for 1.6 lang packs
4179     }
4181     $lang = current_language();
4183     if ($module == '') {
4184         $module = 'moodle';
4185     }
4187     // if $a happens to have % in it, double it so sprintf() doesn't break
4188     if ($a) {
4189         $a = clean_getstring_data( $a );
4190     }
4192 /// Define the two or three major locations of language strings for this module
4194     $locations = array();
4196     if (!empty($extralocations)) {   // Calling code has a good idea where to look
4197         if (is_array($extralocations)) {
4198             $locations += $extralocations;
4199         } else if (is_string($extralocations)) {
4200             $locations[] = $extralocations;
4201         } else {
4202             debugging('Bad lang path provided');
4203         }
4204     }
4206     if (isset($CFG->running_installer)) {
4207         $module = 'installer';
4208         $filetocheck = 'installer.php';
4209         $locations += array( $CFG->dirroot.'/install/lang/', $CFG->dataroot.'/lang/',  $CFG->dirroot.'/lang/' );
4210         $defaultlang = 'en_utf8';
4211     } else {
4212         $locations += array( $CFG->dataroot.'/lang/',  $CFG->dirroot.'/lang/' );
4213     }
4216     if ($module != 'moodle' && $module != 'langconfig') {
4217         if (strpos($module, 'block_') === 0) {  // It's a block lang file
4218             $locations[] =  $CFG->dirroot .'/blocks/'.substr($module, 6).'/lang/';
4219         } else if (strpos($module, 'report_') === 0) {  // It's a report lang file
4220             $locations[] =  $CFG->dirroot .'/'.$CFG->admin.'/report/'.substr($module, 7).'/lang/';
4221             $locations[] =  $CFG->dirroot .'/course/report/'.substr($module, 7).'/lang/';
4222         } else if (strpos($module, 'format_') === 0) {  // Course format
4223             $locations[] =  $CFG->dirroot  .'/course/format/'.substr($module,7).'/lang/';
4224         } else {                                // It's a normal activity
4225             $locations[] =  $CFG->dirroot .'/mod/'.$module.'/lang/';
4226         }
4227     }
4229 /// First check all the normal locations for the string in the current language
4230     foreach ($locations as $location) {
4231         $locallangfile = $location.$lang.'_local'.'/'.$module.'.php';    //first, see if there's a local file
4232         if (file_exists($locallangfile)) {
4233             if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
4234                 eval($result);
4235                 return $resultstring;
4236             }
4237         }
4238         //if local directory not found, or particular string does not exist in local direcotry
4239         $langfile = $location.$lang.'/'.$module.'.php';
4240         if (file_exists($langfile)) {
4241             if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
4242                 eval($result);
4243                 return $resultstring;
4244             }
4245         }
4246     }
4248 /// If the preferred language was English (utf8) we can abort now
4249 /// saving some checks beacuse it's the only "root" lang
4250     if ($lang == 'en_utf8') {
4251         return '[['. $identifier .']]';
4252     }
4254 /// Is a parent language defined?  If so, try to find this string in a parent language file
4256     foreach ($locations as $location) {
4257         $langfile = $location.$lang.'/'.$filetocheck;
4258         if (file_exists($langfile)) {
4259             if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
4260                 eval($result);
4261                 if (!empty($parentlang)) {   // found it!
4263                     //first, see if there's a local file for parent
4264                     $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
4265                     if (file_exists($locallangfile)) {
4266                         if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
4267                             eval($result);
4268                             return $resultstring;
4269                         }
4270                     }
4272                     //if local directory not found, or particular string does not exist in local direcotry
4273                     $langfile = $location.$parentlang.'/'.$module.'.php';
4274                     if (file_exists($langfile)) {
4275                         if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
4276                             eval($result);
4277                             return $resultstring;
4278                         }
4279                     }
4280                 }
4281             }
4282         }
4283     }
4285 /// Our only remaining option is to try English
4287     foreach ($locations as $location) {
4288         $locallangfile = $location.$defaultlang.'_local/'.$module.'.php';    //first, see if there's a local file
4289         if (file_exists($locallangfile)) {
4290             if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
4291                 eval($result);
4292                 return $resultstring;
4293             }
4294         }
4296         //if local_en not found, or string not found in local_en
4297         $langfile = $location.$defaultlang.'/'.$module.'.php';
4299         if (file_exists($langfile)) {
4300             if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
4301                 eval($result);
4302                 return $resultstring;
4303             }
4304         }
4305     }
4307 /// And, because under 1.6 en is defined as en_utf8 child, me must try
4308 /// if it hasn't been queried before.
4309     if ($defaultlang  == 'en') {
4310         $defaultlang = 'en_utf8';
4311         foreach ($locations as $location) {
4312             $locallangfile = $location.$defaultlang.'_local/'.$module.'.php';    //first, see if there's a local file
4313             if (file_exists($locallangfile)) {
4314                 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
4315                     eval($result);
4316                     return $resultstring;
4317                 }
4318             }
4320             //if local_en not found, or string not found in local_en
4321             $langfile = $location.$defaultlang.'/'.$module.'.php';
4323             if (file_exists($langfile)) {
4324                 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
4325                     eval($result);
4326                     return $resultstring;
4327                 }
4328             }
4329         }
4330     }
4332     return '[['.$identifier.']]';  // Last resort
4335 /**
4336  * This function is only used from {@link get_string()}.
4337  *
4338  * @internal Only used from get_string, not meant to be public API
4339  * @param string $identifier ?
4340  * @param string $langfile ?
4341  * @param string $destination ?
4342  * @return string|false ?
4343  * @staticvar array $strings Localized strings
4344  * @access private
4345  * @todo Finish documenting this function.
4346  */
4347 function get_string_from_file($identifier, $langfile, $destination) {
4349     static $strings;    // Keep the strings cached in memory.
4351     if (empty($strings[$langfile])) {
4352         $string = array();
4353         include ($langfile);
4354         $strings[$langfile] = $string;
4355     } else {
4356         $string = &$strings[$langfile];
4357     }
4359     if (!isset ($string[$identifier])) {
4360         return false;
4361     }
4363     return $destination .'= sprintf("'. $string[$identifier] .'");';
4366 /**
4367  * Converts an array of strings to their localized value.
4368  *
4369  * @param array $array An array of strings
4370  * @param string $module The language module that these strings can be found in.
4371  * @return string
4372  */
4373 function get_strings($array, $module='') {
4375    $string = NULL;
4376    foreach ($array as $item) {
4377        $string->$item = get_string($item, $module);
4378    }
4379    return $string;
4382 /**
4383  * Returns a list of language codes and their full names
4384  * hides the _local files from everyone.
4385  * @uses $CFG
4386  * @return array An associative array with contents in the form of LanguageCode => LanguageName
4387  */
4388 function get_list_of_languages() {
4390     global $CFG;
4392     $languages = array();
4394     $filetocheck = 'langconfig.php';
4396     if ( (!defined('FULLME') || FULLME !== 'cron')
4397          && !empty($CFG->langcache) && file_exists($CFG->dataroot .'/cache/languages')) {
4398         // read from cache
4400         $lines = file($CFG->dataroot .'/cache/languages');
4401         foreach ($lines as $line) {
4402             $line = trim($line);
4403             if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
4404                 $languages[$matches[1]] = $matches[2];
4405             }