weekly release 1.9.13+
[moodle.git] / lib / moodlelib.php
blobcac07859b17c6faf842919e3e14bf9a3223c46d7
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 onwards 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
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 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
35 * @package moodlecore
38 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
40 /**
41 * Used by some scripts to check they are being called by Moodle
43 define('MOODLE_INTERNAL', true);
45 /// Date and time constants ///
46 /**
47 * Time constant - the number of seconds in a year
50 define('YEARSECS', 31536000);
52 /**
53 * Time constant - the number of seconds in a week
55 define('WEEKSECS', 604800);
57 /**
58 * Time constant - the number of seconds in a day
60 define('DAYSECS', 86400);
62 /**
63 * Time constant - the number of seconds in an hour
65 define('HOURSECS', 3600);
67 /**
68 * Time constant - the number of seconds in a minute
70 define('MINSECS', 60);
72 /**
73 * Time constant - the number of minutes in a day
75 define('DAYMINS', 1440);
77 /**
78 * Time constant - the number of minutes in an hour
80 define('HOURMINS', 60);
82 /// Parameter constants - every call to optional_param(), required_param() ///
83 /// or clean_param() should have a specified type of parameter. //////////////
85 /**
86 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
87 * originally was 0, but changed because we need to detect unknown
88 * parameter types and swiched order in clean_param().
90 define('PARAM_RAW', 666);
92 /**
93 * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
94 * It was one of the first types, that is why it is abused so much ;-)
96 define('PARAM_CLEAN', 0x0001);
98 /**
99 * PARAM_INT - integers only, use when expecting only numbers.
101 define('PARAM_INT', 0x0002);
104 * PARAM_INTEGER - an alias for PARAM_INT
106 define('PARAM_INTEGER', 0x0002);
109 * PARAM_NUMBER - a real/floating point number.
111 define('PARAM_NUMBER', 0x000a);
114 * PARAM_ALPHA - contains only english letters.
116 define('PARAM_ALPHA', 0x0004);
119 * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
120 * @TODO: should we alias it to PARAM_ALPHANUM ?
122 define('PARAM_ACTION', 0x0004);
125 * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
126 * @TODO: should we alias it to PARAM_ALPHANUM ?
128 define('PARAM_FORMAT', 0x0004);
131 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
133 define('PARAM_NOTAGS', 0x0008);
136 * PARAM_MULTILANG - alias of PARAM_TEXT.
138 define('PARAM_MULTILANG', 0x0009);
141 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
143 define('PARAM_TEXT', 0x0009);
146 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
148 define('PARAM_FILE', 0x0010);
151 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
153 define('PARAM_TAG', 0x0011);
156 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
158 define('PARAM_TAGLIST', 0x0012);
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
164 define('PARAM_PATH', 0x0020);
167 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
169 define('PARAM_HOST', 0x0040);
172 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
174 define('PARAM_URL', 0x0080);
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!)
179 define('PARAM_LOCALURL', 0x0180);
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
185 define('PARAM_CLEANFILE',0x0200);
188 * PARAM_ALPHANUM - expected numbers and letters only.
190 define('PARAM_ALPHANUM', 0x0400);
193 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
195 define('PARAM_BOOL', 0x0800);
198 * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
199 * note: do not forget to addslashes() before storing into database!
201 define('PARAM_CLEANHTML',0x1000);
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??
208 define('PARAM_ALPHAEXT', 0x2000);
211 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
213 define('PARAM_SAFEDIR', 0x4000);
216 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
218 define('PARAM_SEQUENCE', 0x8000);
221 * PARAM_PEM - Privacy Enhanced Mail format
223 define('PARAM_PEM', 0x10000);
226 * PARAM_BASE64 - Base 64 encoded format
228 define('PARAM_BASE64', 0x20000);
231 /// Page types ///
233 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
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 for now, do show recoverable fatal errors */
245 define ('DEBUG_ALL', 6143);
246 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
247 define ('DEBUG_DEVELOPER', 38911);
250 * Blog access level constant declaration
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);
259 * Tag constanst
261 //To prevent problems with multibytes strings, this should not exceed the
262 //length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
263 define('TAG_MAX_LENGTH', 50);
266 * Password policy constants
268 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
269 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
270 define ('PASSWORD_DIGITS', '0123456789');
271 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
273 if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
274 define('SORT_LOCALE_STRING', SORT_STRING);
278 /// PARAMETER HANDLING ////////////////////////////////////////////////////
281 * Returns a particular value for the named variable, taken from
282 * POST or GET. If the parameter doesn't exist then an error is
283 * thrown because we require this variable.
285 * This function should be used to initialise all required values
286 * in a script that are based on parameters. Usually it will be
287 * used like this:
288 * $id = required_param('id');
290 * @param string $parname the name of the page parameter we want
291 * @param int $type expected type of parameter
292 * @return mixed
294 function required_param($parname, $type=PARAM_CLEAN) {
296 // detect_unchecked_vars addition
297 global $CFG;
298 if (!empty($CFG->detect_unchecked_vars)) {
299 global $UNCHECKED_VARS;
300 unset ($UNCHECKED_VARS->vars[$parname]);
303 if (isset($_POST[$parname])) { // POST has precedence
304 $param = $_POST[$parname];
305 } else if (isset($_GET[$parname])) {
306 $param = $_GET[$parname];
307 } else {
308 error('A required parameter ('.$parname.') was missing');
311 return clean_param($param, $type);
315 * Returns a particular value for the named variable, taken from
316 * POST or GET, otherwise returning a given default.
318 * This function should be used to initialise all optional values
319 * in a script that are based on parameters. Usually it will be
320 * used like this:
321 * $name = optional_param('name', 'Fred');
323 * @param string $parname the name of the page parameter we want
324 * @param mixed $default the default value to return if nothing is found
325 * @param int $type expected type of parameter
326 * @return mixed
328 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
330 // detect_unchecked_vars addition
331 global $CFG;
332 if (!empty($CFG->detect_unchecked_vars)) {
333 global $UNCHECKED_VARS;
334 unset ($UNCHECKED_VARS->vars[$parname]);
337 if (isset($_POST[$parname])) { // POST has precedence
338 $param = $_POST[$parname];
339 } else if (isset($_GET[$parname])) {
340 $param = $_GET[$parname];
341 } else {
342 return $default;
345 return clean_param($param, $type);
349 * Used by {@link optional_param()} and {@link required_param()} to
350 * clean the variables and/or cast to specific types, based on
351 * an options field.
352 * <code>
353 * $course->format = clean_param($course->format, PARAM_ALPHA);
354 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
355 * </code>
357 * @uses $CFG
358 * @uses PARAM_RAW
359 * @uses PARAM_CLEAN
360 * @uses PARAM_CLEANHTML
361 * @uses PARAM_INT
362 * @uses PARAM_NUMBER
363 * @uses PARAM_ALPHA
364 * @uses PARAM_ALPHANUM
365 * @uses PARAM_ALPHAEXT
366 * @uses PARAM_SEQUENCE
367 * @uses PARAM_BOOL
368 * @uses PARAM_NOTAGS
369 * @uses PARAM_TEXT
370 * @uses PARAM_SAFEDIR
371 * @uses PARAM_CLEANFILE
372 * @uses PARAM_FILE
373 * @uses PARAM_PATH
374 * @uses PARAM_HOST
375 * @uses PARAM_URL
376 * @uses PARAM_LOCALURL
377 * @uses PARAM_PEM
378 * @uses PARAM_BASE64
379 * @uses PARAM_TAG
380 * @uses PARAM_SEQUENCE
381 * @param mixed $param the variable we are cleaning
382 * @param int $type expected format of param after cleaning.
383 * @return mixed
385 function clean_param($param, $type) {
387 global $CFG;
389 if (is_array($param)) { // Let's loop
390 $newparam = array();
391 foreach ($param as $key => $value) {
392 $newparam[$key] = clean_param($value, $type);
394 return $newparam;
397 switch ($type) {
398 case PARAM_RAW: // no cleaning at all
399 return $param;
401 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
402 if (is_numeric($param)) {
403 return $param;
405 $param = stripslashes($param); // Needed for kses to work fine
406 $param = clean_text($param); // Sweep for scripts, etc
407 return addslashes($param); // Restore original request parameter slashes
409 case PARAM_CLEANHTML: // prepare html fragment for display, do not store it into db!!
410 $param = stripslashes($param); // Remove any slashes
411 $param = clean_text($param); // Sweep for scripts, etc
412 return trim($param);
414 case PARAM_INT:
415 return (int)$param; // Convert to integer
417 case PARAM_NUMBER:
418 return (float)$param; // Convert to integer
420 case PARAM_ALPHA: // Remove everything not a-z
421 return preg_replace('/[^a-zA-Z]/i', '', $param);
423 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
424 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
426 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z/_-
427 return preg_replace('/[^a-zA-Z\/_-]/i', '', $param);
429 case PARAM_SEQUENCE: // Remove everything not 0-9,
430 return preg_replace('/[^0-9,]/i', '', $param);
432 case PARAM_BOOL: // Convert to 1 or 0
433 $tempstr = strtolower($param);
434 if ($tempstr == 'on' or $tempstr == 'yes' ) {
435 $param = 1;
436 } else if ($tempstr == 'off' or $tempstr == 'no') {
437 $param = 0;
438 } else {
439 $param = empty($param) ? 0 : 1;
441 return $param;
443 case PARAM_NOTAGS: // Strip all tags
444 return strip_tags($param);
446 case PARAM_TEXT: // leave only tags needed for multilang
447 return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
449 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
450 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
452 case PARAM_CLEANFILE: // allow only safe characters
453 return clean_filename($param);
455 case PARAM_FILE: // Strip all suspicious characters from filename
456 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
457 $param = preg_replace('~\.\.+~', '', $param);
458 if ($param === '.') {
459 $param = '';
461 return $param;
463 case PARAM_PATH: // Strip all suspicious characters from file path
464 $param = str_replace('\\', '/', $param);
465 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
466 $param = preg_replace('~\.\.+~', '', $param);
467 $param = preg_replace('~//+~', '/', $param);
468 return preg_replace('~/(\./)+~', '/', $param);
470 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
471 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
472 // match ipv4 dotted quad
473 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
474 // confirm values are ok
475 if ( $match[0] > 255
476 || $match[1] > 255
477 || $match[3] > 255
478 || $match[4] > 255 ) {
479 // hmmm, what kind of dotted quad is this?
480 $param = '';
482 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
483 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
484 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
486 // all is ok - $param is respected
487 } else {
488 // all is not ok...
489 $param='';
491 return $param;
493 case PARAM_URL: // allow safe ftp, http, mailto urls
494 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
495 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
496 // all is ok, param is respected
497 } else {
498 $param =''; // not really ok
500 return $param;
502 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
503 $param = clean_param($param, PARAM_URL);
504 if (!empty($param)) {
505 if (preg_match(':^/:', $param)) {
506 // root-relative, ok!
507 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
508 // absolute, and matches our wwwroot
509 } else {
510 // relative - let's make sure there are no tricks
511 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
512 // looks ok.
513 } else {
514 $param = '';
518 return $param;
520 case PARAM_PEM:
521 $param = trim($param);
522 // PEM formatted strings may contain letters/numbers and the symbols
523 // forward slash: /
524 // plus sign: +
525 // equal sign: =
526 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
527 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
528 list($wholething, $body) = $matches;
529 unset($wholething, $matches);
530 $b64 = clean_param($body, PARAM_BASE64);
531 if (!empty($b64)) {
532 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
533 } else {
534 return '';
537 return '';
539 case PARAM_BASE64:
540 if (!empty($param)) {
541 // PEM formatted strings may contain letters/numbers and the symbols
542 // forward slash: /
543 // plus sign: +
544 // equal sign: =
545 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
546 return '';
548 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
549 // Each line of base64 encoded data must be 64 characters in
550 // length, except for the last line which may be less than (or
551 // equal to) 64 characters long.
552 for ($i=0, $j=count($lines); $i < $j; $i++) {
553 if ($i + 1 == $j) {
554 if (64 < strlen($lines[$i])) {
555 return '';
557 continue;
560 if (64 != strlen($lines[$i])) {
561 return '';
564 return implode("\n",$lines);
565 } else {
566 return '';
569 case PARAM_TAG:
570 // Please note it is not safe to use the tag name directly anywhere,
571 // it must be processed with s(), urlencode() before embedding anywhere.
572 // remove some nasties
573 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
574 //as long as magic_quotes_gpc is used, a backslash will be a
575 //problem, so remove *all* backslash - BUT watch out for SQL injections caused by this sloppy design (skodak)
576 $param = str_replace('\\', '', $param);
577 //convert many whitespace chars into one
578 $param = preg_replace('/\s+/', ' ', $param);
579 $textlib = textlib_get_instance();
580 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
581 return $param;
583 case PARAM_TAGLIST:
584 $tags = explode(',', $param);
585 $result = array();
586 foreach ($tags as $tag) {
587 $res = clean_param($tag, PARAM_TAG);
588 if ($res != '') {
589 $result[] = $res;
592 if ($result) {
593 return implode(',', $result);
594 } else {
595 return '';
598 default: // throw error, switched parameters in optional_param or another serious problem
599 error("Unknown parameter type: $type");
604 * Return true if given value is integer or string with integer value
606 * @param mixed $value String or Int
607 * @return bool true if number, false if not
609 function is_number($value) {
610 if (is_int($value)) {
611 return true;
612 } else if (is_string($value)) {
613 return ((string)(int)$value) === $value;
614 } else {
615 return false;
620 * This function is useful for testing whether something you got back from
621 * the HTML editor actually contains anything. Sometimes the HTML editor
622 * appear to be empty, but actually you get back a <br> tag or something.
624 * @param string $string a string containing HTML.
625 * @return boolean does the string contain any actual content - that is text,
626 * images, objcts, etc.
628 function html_is_blank($string) {
629 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
633 * Set a key in global configuration
635 * Set a key/value pair in both this session's {@link $CFG} global variable
636 * and in the 'config' database table for future sessions.
638 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
639 * In that case it doesn't affect $CFG.
641 * A NULL value will delete the entry.
643 * @param string $name the key to set
644 * @param string $value the value to set (without magic quotes)
645 * @param string $plugin (optional) the plugin scope
646 * @uses $CFG
647 * @return bool
649 function set_config($name, $value, $plugin=NULL) {
650 /// No need for get_config because they are usually always available in $CFG
652 global $CFG;
654 if (empty($plugin)) {
655 if (!array_key_exists($name, $CFG->config_php_settings)) {
656 // So it's defined for this invocation at least
657 if (is_null($value)) {
658 unset($CFG->$name);
659 } else {
660 $CFG->$name = (string)$value; // settings from db are always strings
664 if (get_field('config', 'name', 'name', $name)) {
665 if ($value===null) {
666 return delete_records('config', 'name', $name);
667 } else {
668 return set_field('config', 'value', addslashes($value), 'name', $name);
670 } else {
671 if ($value===null) {
672 return true;
674 $config = new object();
675 $config->name = $name;
676 $config->value = addslashes($value);
677 return insert_record('config', $config);
679 } else { // plugin scope
680 if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
681 if ($value===null) {
682 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
683 } else {
684 return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
686 } else {
687 if ($value===null) {
688 return true;
690 $config = new object();
691 $config->plugin = addslashes($plugin);
692 $config->name = $name;
693 $config->value = addslashes($value);
694 return insert_record('config_plugins', $config);
700 * Get configuration values from the global config table
701 * or the config_plugins table.
703 * If called with no parameters it will do the right thing
704 * generating $CFG safely from the database without overwriting
705 * existing values.
707 * If called with 2 parameters it will return a $string single
708 * value or false of the value is not found.
710 * @param string $plugin
711 * @param string $name
712 * @uses $CFG
713 * @return hash-like object or single value
716 function get_config($plugin=NULL, $name=NULL) {
718 global $CFG;
720 if (!empty($name)) { // the user is asking for a specific value
721 if (!empty($plugin)) {
722 return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
723 } else {
724 return get_field('config', 'value', 'name', $name);
728 // the user is after a recordset
729 if (!empty($plugin)) {
730 if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
731 $configs = (array)$configs;
732 $localcfg = array();
733 foreach ($configs as $config) {
734 $localcfg[$config->name] = $config->value;
736 return (object)$localcfg;
737 } else {
738 return false;
740 } else {
741 // this was originally in setup.php
742 if ($configs = get_records('config')) {
743 $localcfg = (array)$CFG;
744 foreach ($configs as $config) {
745 if (!isset($localcfg[$config->name])) {
746 $localcfg[$config->name] = $config->value;
748 // do not complain anymore if config.php overrides settings from db
751 $localcfg = (object)$localcfg;
752 return $localcfg;
753 } else {
754 // preserve $CFG if DB returns nothing or error
755 return $CFG;
762 * Removes a key from global configuration
764 * @param string $name the key to set
765 * @param string $plugin (optional) the plugin scope
766 * @uses $CFG
767 * @return bool
769 function unset_config($name, $plugin=NULL) {
771 global $CFG;
773 unset($CFG->$name);
775 if (empty($plugin)) {
776 return delete_records('config', 'name', $name);
777 } else {
778 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
783 * Get volatile flags
785 * @param string $type
786 * @param int $changedsince
787 * @return records array
790 function get_cache_flags($type, $changedsince=NULL) {
792 $type = addslashes($type);
794 $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
795 if ($changedsince !== NULL) {
796 $changedsince = (int)$changedsince;
797 $sqlwhere .= ' AND timemodified > ' . $changedsince;
799 $cf = array();
800 if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
801 foreach ($flags as $flag) {
802 $cf[$flag->name] = $flag->value;
805 return $cf;
809 * Use this funciton to get a list of users from a config setting of type admin_setting_users_with_capability.
810 * @param string $value the value of the config setting.
811 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
812 * @return array of user objects.
814 function get_users_from_config($value, $capability) {
815 global $CFG;
816 if ($value == '$@ALL@$') {
817 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
818 } else if ($value) {
819 $usernames = explode(',', $value);
820 $users = get_records_select('user', "username IN ('" . implode("','", $usernames) . "') AND mnethostid = " . $CFG->mnet_localhost_id);
821 } else {
822 $users = array();
824 return $users;
828 * Get volatile flags
830 * @param string $type
831 * @param string $name
832 * @param int $changedsince
833 * @return records array
836 function get_cache_flag($type, $name, $changedsince=NULL) {
838 $type = addslashes($type);
839 $name = addslashes($name);
841 $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
842 if ($changedsince !== NULL) {
843 $changedsince = (int)$changedsince;
844 $sqlwhere .= ' AND timemodified > ' . $changedsince;
846 return get_field_select('cache_flags', 'value', $sqlwhere);
850 * Set a volatile flag
852 * @param string $type the "type" namespace for the key
853 * @param string $name the key to set
854 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
855 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
856 * @return bool
858 function set_cache_flag($type, $name, $value, $expiry=NULL) {
861 $timemodified = time();
862 if ($expiry===NULL || $expiry < $timemodified) {
863 $expiry = $timemodified + 24 * 60 * 60;
864 } else {
865 $expiry = (int)$expiry;
868 if ($value === NULL) {
869 return unset_cache_flag($type,$name);
872 $type = addslashes($type);
873 $name = addslashes($name);
874 if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
875 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
876 return true; //no need to update; helps rcache too
878 $f->value = addslashes($value);
879 $f->expiry = $expiry;
880 $f->timemodified = $timemodified;
881 return update_record('cache_flags', $f);
882 } else {
883 $f = new object();
884 $f->flagtype = $type;
885 $f->name = $name;
886 $f->value = addslashes($value);
887 $f->expiry = $expiry;
888 $f->timemodified = $timemodified;
889 return (bool)insert_record('cache_flags', $f);
894 * Removes a single volatile flag
896 * @param string $type the "type" namespace for the key
897 * @param string $name the key to set
898 * @uses $CFG
899 * @return bool
901 function unset_cache_flag($type, $name) {
903 return delete_records('cache_flags',
904 'name', addslashes($name),
905 'flagtype', addslashes($type));
909 * Garbage-collect volatile flags
912 function gc_cache_flags() {
913 return delete_records_select('cache_flags', 'expiry < ' . time());
917 * Refresh current $USER session global variable with all their current preferences.
918 * @uses $USER
920 function reload_user_preferences() {
922 global $USER;
924 //reset preference
925 $USER->preference = array();
927 if (!isloggedin() or isguestuser()) {
928 // no permanent storage for not-logged-in user and guest
930 } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
931 foreach ($preferences as $preference) {
932 $USER->preference[$preference->name] = $preference->value;
936 return true;
940 * Sets a preference for the current user
941 * Optionally, can set a preference for a different user object
942 * @uses $USER
943 * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
945 * @param string $name The key to set as preference for the specified user
946 * @param string $value The value to set forthe $name key in the specified user's record
947 * @param int $otheruserid A moodle user ID
948 * @return bool
950 function set_user_preference($name, $value, $otheruserid=NULL) {
952 global $USER;
954 if (!isset($USER->preference)) {
955 reload_user_preferences();
958 if (empty($name)) {
959 return false;
962 $nostore = false;
964 if (empty($otheruserid)){
965 if (!isloggedin() or isguestuser()) {
966 $nostore = true;
968 $userid = $USER->id;
969 } else {
970 if (isguestuser($otheruserid)) {
971 $nostore = true;
973 $userid = $otheruserid;
976 $return = true;
977 if ($nostore) {
978 // no permanent storage for not-logged-in user and guest
980 } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
981 if ($preference->value === $value) {
982 return true;
984 if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
985 $return = false;
988 } else {
989 $preference = new object();
990 $preference->userid = $userid;
991 $preference->name = addslashes($name);
992 $preference->value = addslashes((string)$value);
993 if (!insert_record('user_preferences', $preference)) {
994 $return = false;
998 // update value in USER session if needed
999 if ($userid == $USER->id) {
1000 $USER->preference[$name] = (string)$value;
1003 return $return;
1007 * Unsets a preference completely by deleting it from the database
1008 * Optionally, can set a preference for a different user id
1009 * @uses $USER
1010 * @param string $name The key to unset as preference for the specified user
1011 * @param int $otheruserid A moodle user ID
1013 function unset_user_preference($name, $otheruserid=NULL) {
1015 global $USER;
1017 if (!isset($USER->preference)) {
1018 reload_user_preferences();
1021 if (empty($otheruserid)){
1022 $userid = $USER->id;
1023 } else {
1024 $userid = $otheruserid;
1027 //Delete the preference from $USER if needed
1028 if ($userid == $USER->id) {
1029 unset($USER->preference[$name]);
1032 //Then from DB
1033 return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
1038 * Sets a whole array of preferences for the current user
1039 * @param array $prefarray An array of key/value pairs to be set
1040 * @param int $otheruserid A moodle user ID
1041 * @return bool
1043 function set_user_preferences($prefarray, $otheruserid=NULL) {
1045 if (!is_array($prefarray) or empty($prefarray)) {
1046 return false;
1049 $return = true;
1050 foreach ($prefarray as $name => $value) {
1051 // The order is important; test for return is done first
1052 $return = (set_user_preference($name, $value, $otheruserid) && $return);
1054 return $return;
1058 * If no arguments are supplied this function will return
1059 * all of the current user preferences as an array.
1060 * If a name is specified then this function
1061 * attempts to return that particular preference value. If
1062 * none is found, then the optional value $default is returned,
1063 * otherwise NULL.
1064 * @param string $name Name of the key to use in finding a preference value
1065 * @param string $default Value to be returned if the $name key is not set in the user preferences
1066 * @param int $otheruserid A moodle user ID
1067 * @uses $USER
1068 * @return string
1070 function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
1071 global $USER;
1073 if (!isset($USER->preference)) {
1074 reload_user_preferences();
1077 if (empty($otheruserid)){
1078 $userid = $USER->id;
1079 } else {
1080 $userid = $otheruserid;
1083 if ($userid == $USER->id) {
1084 $preference = $USER->preference;
1086 } else {
1087 $preference = array();
1088 if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
1089 foreach ($prefdata as $pref) {
1090 $preference[$pref->name] = $pref->value;
1095 if (empty($name)) {
1096 return $preference; // All values
1098 } else if (array_key_exists($name, $preference)) {
1099 return $preference[$name]; // The single value
1101 } else {
1102 return $default; // Default value (or NULL)
1107 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1110 * Given date parts in user time produce a GMT timestamp.
1112 * @param int $year The year part to create timestamp of
1113 * @param int $month The month part to create timestamp of
1114 * @param int $day The day part to create timestamp of
1115 * @param int $hour The hour part to create timestamp of
1116 * @param int $minute The minute part to create timestamp of
1117 * @param int $second The second part to create timestamp of
1118 * @param mixed $timezone Timezone modifier, if 99 then use default user's timezone
1119 * @param bool $applydst Toggle Daylight Saving Time, default true, will be
1120 * applied only if timezone is 99 or string.
1121 * @return int timestamp
1122 * @todo Finish documenting this function
1124 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1126 //save input timezone, required for dst offset check.
1127 $passedtimezone = $timezone;
1129 $timezone = get_user_timezone_offset($timezone);
1131 if (abs($timezone) > 13) { //server time
1132 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1133 } else {
1134 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1135 $time = usertime($time, $timezone);
1137 //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
1138 if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
1139 $time -= dst_offset_on($time, $passedtimezone);
1143 return $time;
1148 * Given an amount of time in seconds, returns string
1149 * formatted nicely as weeks, days, hours etc as needed
1151 * @uses MINSECS
1152 * @uses HOURSECS
1153 * @uses DAYSECS
1154 * @uses YEARSECS
1155 * @param int $totalsecs ?
1156 * @param array $str ?
1157 * @return string
1159 function format_time($totalsecs, $str=NULL) {
1161 $totalsecs = abs($totalsecs);
1163 if (!$str) { // Create the str structure the slow way
1164 $str->day = get_string('day');
1165 $str->days = get_string('days');
1166 $str->hour = get_string('hour');
1167 $str->hours = get_string('hours');
1168 $str->min = get_string('min');
1169 $str->mins = get_string('mins');
1170 $str->sec = get_string('sec');
1171 $str->secs = get_string('secs');
1172 $str->year = get_string('year');
1173 $str->years = get_string('years');
1177 $years = floor($totalsecs/YEARSECS);
1178 $remainder = $totalsecs - ($years*YEARSECS);
1179 $days = floor($remainder/DAYSECS);
1180 $remainder = $totalsecs - ($days*DAYSECS);
1181 $hours = floor($remainder/HOURSECS);
1182 $remainder = $remainder - ($hours*HOURSECS);
1183 $mins = floor($remainder/MINSECS);
1184 $secs = $remainder - ($mins*MINSECS);
1186 $ss = ($secs == 1) ? $str->sec : $str->secs;
1187 $sm = ($mins == 1) ? $str->min : $str->mins;
1188 $sh = ($hours == 1) ? $str->hour : $str->hours;
1189 $sd = ($days == 1) ? $str->day : $str->days;
1190 $sy = ($years == 1) ? $str->year : $str->years;
1192 $oyears = '';
1193 $odays = '';
1194 $ohours = '';
1195 $omins = '';
1196 $osecs = '';
1198 if ($years) $oyears = $years .' '. $sy;
1199 if ($days) $odays = $days .' '. $sd;
1200 if ($hours) $ohours = $hours .' '. $sh;
1201 if ($mins) $omins = $mins .' '. $sm;
1202 if ($secs) $osecs = $secs .' '. $ss;
1204 if ($years) return trim($oyears .' '. $odays);
1205 if ($days) return trim($odays .' '. $ohours);
1206 if ($hours) return trim($ohours .' '. $omins);
1207 if ($mins) return trim($omins .' '. $osecs);
1208 if ($secs) return $osecs;
1209 return get_string('now');
1213 * Returns a formatted string that represents a date in user time
1214 * <b>WARNING: note that the format is for strftime(), not date().</b>
1215 * Because of a bug in most Windows time libraries, we can't use
1216 * the nicer %e, so we have to use %d which has leading zeroes.
1217 * A lot of the fuss in the function is just getting rid of these leading
1218 * zeroes as efficiently as possible.
1220 * If parameter fixday = true (default), then take off leading
1221 * zero from %d, else mantain it.
1223 * @uses HOURSECS
1224 * @param int $date timestamp in GMT
1225 * @param string $format strftime format
1226 * @param mixed $timezone by default, uses the user's time zone. if numeric and
1227 * not 99 then daylight saving will not be added.
1228 * @param bool $fixday If true (default) then the leading
1229 * zero from %d is removed. If false then the leading zero is mantained.
1230 * @return string
1232 function userdate($date, $format='', $timezone=99, $fixday = true) {
1234 global $CFG;
1236 if (empty($format)) {
1237 $format = get_string('strftimedaydatetime');
1240 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
1241 $fixday = false;
1242 } else if ($fixday) {
1243 $formatnoday = str_replace('%d', 'DD', $format);
1244 $fixday = ($formatnoday != $format);
1247 //add daylight saving offset for string timezones only, as we can't get dst for
1248 //float values. if timezone is 99 (user default timezone), then try update dst.
1249 if ((99 == $timezone) || !is_numeric($timezone)) {
1250 $date += dst_offset_on($date, $timezone);
1253 $timezone = get_user_timezone_offset($timezone);
1255 if (abs($timezone) > 13) { /// Server time
1256 if ($fixday) {
1257 $datestring = strftime($formatnoday, $date);
1258 $daystring = str_replace(' 0', '', strftime(' %d', $date));
1259 $datestring = str_replace('DD', $daystring, $datestring);
1260 } else {
1261 $datestring = strftime($format, $date);
1263 } else {
1264 $date += (int)($timezone * 3600);
1265 if ($fixday) {
1266 $datestring = gmstrftime($formatnoday, $date);
1267 $daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
1268 $datestring = str_replace('DD', $daystring, $datestring);
1269 } else {
1270 $datestring = gmstrftime($format, $date);
1274 /// If we are running under Windows convert from windows encoding to UTF-8
1275 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1277 if ($CFG->ostype == 'WINDOWS') {
1278 if ($localewincharset = get_string('localewincharset')) {
1279 $textlib = textlib_get_instance();
1280 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1284 return $datestring;
1288 * Given a $time timestamp in GMT (seconds since epoch),
1289 * returns an array that represents the date in user time
1291 * @uses HOURSECS
1292 * @param int $time Timestamp in GMT
1293 * @param mixed $timezone offset time with timezone, if float and not 99, then no
1294 * dst offset is applyed
1295 * @return array An array that represents the date in user time
1296 * @todo Finish documenting this function
1298 function usergetdate($time, $timezone=99) {
1300 //save input timezone, required for dst offset check.
1301 $passedtimezone = $timezone;
1303 $timezone = get_user_timezone_offset($timezone);
1305 if (abs($timezone) > 13) { // Server time
1306 return getdate($time);
1309 //add daylight saving offset for string timezones only, as we can't get dst for
1310 //float values. if timezone is 99 (user default timezone), then try update dst.
1311 if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
1312 $time += dst_offset_on($time, $passedtimezone);
1315 $time += intval((float)$timezone * HOURSECS);
1317 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
1319 //be careful to ensure the returned array matches that produced by getdate() above
1320 list(
1321 $getdate['month'],
1322 $getdate['weekday'],
1323 $getdate['yday'],
1324 $getdate['year'],
1325 $getdate['mon'],
1326 $getdate['wday'],
1327 $getdate['mday'],
1328 $getdate['hours'],
1329 $getdate['minutes'],
1330 $getdate['seconds']
1331 ) = explode('_', $datestring);
1333 return $getdate;
1337 * Given a GMT timestamp (seconds since epoch), offsets it by
1338 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1340 * @uses HOURSECS
1341 * @param int $date Timestamp in GMT
1342 * @param float $timezone
1343 * @return int
1345 function usertime($date, $timezone=99) {
1347 $timezone = get_user_timezone_offset($timezone);
1349 if (abs($timezone) > 13) {
1350 return $date;
1352 return $date - (int)($timezone * HOURSECS);
1356 * Given a time, return the GMT timestamp of the most recent midnight
1357 * for the current user.
1359 * @param int $date Timestamp in GMT
1360 * @param float $timezone ?
1361 * @return ?
1363 function usergetmidnight($date, $timezone=99) {
1365 $userdate = usergetdate($date, $timezone);
1367 // Time of midnight of this user's day, in GMT
1368 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1373 * Returns a string that prints the user's timezone
1375 * @param float $timezone The user's timezone
1376 * @return string
1378 function usertimezone($timezone=99) {
1380 $tz = get_user_timezone($timezone);
1382 if (!is_float($tz)) {
1383 return $tz;
1386 if(abs($tz) > 13) { // Server time
1387 return get_string('serverlocaltime');
1390 if($tz == intval($tz)) {
1391 // Don't show .0 for whole hours
1392 $tz = intval($tz);
1395 if($tz == 0) {
1396 return 'UTC';
1398 else if($tz > 0) {
1399 return 'UTC+'.$tz;
1401 else {
1402 return 'UTC'.$tz;
1408 * Returns a float which represents the user's timezone difference from GMT in hours
1409 * Checks various settings and picks the most dominant of those which have a value
1411 * @uses $CFG
1412 * @uses $USER
1413 * @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
1414 * @return int
1416 function get_user_timezone_offset($tz = 99) {
1418 global $USER, $CFG;
1420 $tz = get_user_timezone($tz);
1422 if (is_float($tz)) {
1423 return $tz;
1424 } else {
1425 $tzrecord = get_timezone_record($tz);
1426 if (empty($tzrecord)) {
1427 return 99.0;
1429 return (float)$tzrecord->gmtoff / HOURMINS;
1434 * Returns an int which represents the systems's timezone difference from GMT in seconds
1435 * @param mixed $tz timezone
1436 * @return int if found, false is timezone 99 or error
1438 function get_timezone_offset($tz) {
1439 global $CFG;
1441 if ($tz == 99) {
1442 return false;
1445 if (is_numeric($tz)) {
1446 return intval($tz * 60*60);
1449 if (!$tzrecord = get_timezone_record($tz)) {
1450 return false;
1452 return intval($tzrecord->gmtoff * 60);
1456 * Returns a float or a string which denotes the user's timezone
1457 * 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)
1458 * means that for this timezone there are also DST rules to be taken into account
1459 * Checks various settings and picks the most dominant of those which have a value
1461 * @uses $USER
1462 * @uses $CFG
1463 * @param mixed $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
1464 * @return mixed
1466 function get_user_timezone($tz = 99) {
1467 global $USER, $CFG;
1469 $timezones = array(
1470 $tz,
1471 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1472 isset($USER->timezone) ? $USER->timezone : 99,
1473 isset($CFG->timezone) ? $CFG->timezone : 99,
1476 $tz = 99;
1478 while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
1479 $tz = $next['value'];
1482 return is_numeric($tz) ? (float) $tz : $tz;
1488 * @uses $CFG
1489 * @uses $db
1490 * @param string $timezonename ?
1491 * @return object
1493 function get_timezone_record($timezonename) {
1494 global $CFG, $db;
1495 static $cache = NULL;
1497 if ($cache === NULL) {
1498 $cache = array();
1501 if (isset($cache[$timezonename])) {
1502 return $cache[$timezonename];
1505 return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1506 WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1512 * @uses $CFG
1513 * @uses $USER
1514 * @param ? $fromyear ?
1515 * @param ? $to_year ?
1516 * @return bool
1518 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1519 global $CFG, $SESSION;
1521 $usertz = get_user_timezone($strtimezone);
1523 if (is_float($usertz)) {
1524 // Trivial timezone, no DST
1525 return false;
1528 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1529 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1530 unset($SESSION->dst_offsets);
1531 unset($SESSION->dst_range);
1534 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1535 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1536 // This will be the return path most of the time, pretty light computationally
1537 return true;
1540 // Reaching here means we either need to extend our table or create it from scratch
1542 // Remember which TZ we calculated these changes for
1543 $SESSION->dst_offsettz = $usertz;
1545 if(empty($SESSION->dst_offsets)) {
1546 // If we 're creating from scratch, put the two guard elements in there
1547 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1549 if(empty($SESSION->dst_range)) {
1550 // If creating from scratch
1551 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1552 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
1554 // Fill in the array with the extra years we need to process
1555 $yearstoprocess = array();
1556 for($i = $from; $i <= $to; ++$i) {
1557 $yearstoprocess[] = $i;
1560 // Take note of which years we have processed for future calls
1561 $SESSION->dst_range = array($from, $to);
1563 else {
1564 // If needing to extend the table, do the same
1565 $yearstoprocess = array();
1567 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1568 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
1570 if($from < $SESSION->dst_range[0]) {
1571 // Take note of which years we need to process and then note that we have processed them for future calls
1572 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1573 $yearstoprocess[] = $i;
1575 $SESSION->dst_range[0] = $from;
1577 if($to > $SESSION->dst_range[1]) {
1578 // Take note of which years we need to process and then note that we have processed them for future calls
1579 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1580 $yearstoprocess[] = $i;
1582 $SESSION->dst_range[1] = $to;
1586 if(empty($yearstoprocess)) {
1587 // This means that there was a call requesting a SMALLER range than we have already calculated
1588 return true;
1591 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1592 // Also, the array is sorted in descending timestamp order!
1594 // Get DB data
1596 static $presets_cache = array();
1597 if (!isset($presets_cache[$usertz])) {
1598 $presets_cache[$usertz] = 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');
1600 if(empty($presets_cache[$usertz])) {
1601 return false;
1604 // Remove ending guard (first element of the array)
1605 reset($SESSION->dst_offsets);
1606 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1608 // Add all required change timestamps
1609 foreach($yearstoprocess as $y) {
1610 // Find the record which is in effect for the year $y
1611 foreach($presets_cache[$usertz] as $year => $preset) {
1612 if($year <= $y) {
1613 break;
1617 $changes = dst_changes_for_year($y, $preset);
1619 if($changes === NULL) {
1620 continue;
1622 if($changes['dst'] != 0) {
1623 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1625 if($changes['std'] != 0) {
1626 $SESSION->dst_offsets[$changes['std']] = 0;
1630 // Put in a guard element at the top
1631 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1632 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1634 // Sort again
1635 krsort($SESSION->dst_offsets);
1637 return true;
1640 function dst_changes_for_year($year, $timezone) {
1642 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1643 return NULL;
1646 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1647 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1649 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1650 list($std_hour, $std_min) = explode(':', $timezone->std_time);
1652 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1653 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1655 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1656 // This has the advantage of being able to have negative values for hour, i.e. for timezones
1657 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1659 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1660 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1662 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1665 // $time must NOT be compensated at all, it has to be a pure timestamp
1666 function dst_offset_on($time, $strtimezone = NULL) {
1667 global $SESSION;
1669 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
1670 return 0;
1673 reset($SESSION->dst_offsets);
1674 while(list($from, $offset) = each($SESSION->dst_offsets)) {
1675 if($from <= $time) {
1676 break;
1680 // This is the normal return path
1681 if($offset !== NULL) {
1682 return $offset;
1685 // Reaching this point means we haven't calculated far enough, do it now:
1686 // Calculate extra DST changes if needed and recurse. The recursion always
1687 // moves toward the stopping condition, so will always end.
1689 if($from == 0) {
1690 // We need a year smaller than $SESSION->dst_range[0]
1691 if($SESSION->dst_range[0] == 1971) {
1692 return 0;
1694 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
1695 return dst_offset_on($time, $strtimezone);
1697 else {
1698 // We need a year larger than $SESSION->dst_range[1]
1699 if($SESSION->dst_range[1] == 2035) {
1700 return 0;
1702 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
1703 return dst_offset_on($time, $strtimezone);
1707 function find_day_in_month($startday, $weekday, $month, $year) {
1709 $daysinmonth = days_in_month($month, $year);
1711 if($weekday == -1) {
1712 // Don't care about weekday, so return:
1713 // abs($startday) if $startday != -1
1714 // $daysinmonth otherwise
1715 return ($startday == -1) ? $daysinmonth : abs($startday);
1718 // From now on we 're looking for a specific weekday
1720 // Give "end of month" its actual value, since we know it
1721 if($startday == -1) {
1722 $startday = -1 * $daysinmonth;
1725 // Starting from day $startday, the sign is the direction
1727 if($startday < 1) {
1729 $startday = abs($startday);
1730 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1732 // This is the last such weekday of the month
1733 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1734 if($lastinmonth > $daysinmonth) {
1735 $lastinmonth -= 7;
1738 // Find the first such weekday <= $startday
1739 while($lastinmonth > $startday) {
1740 $lastinmonth -= 7;
1743 return $lastinmonth;
1746 else {
1748 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1750 $diff = $weekday - $indexweekday;
1751 if($diff < 0) {
1752 $diff += 7;
1755 // This is the first such weekday of the month equal to or after $startday
1756 $firstfromindex = $startday + $diff;
1758 return $firstfromindex;
1764 * Calculate the number of days in a given month
1766 * @param int $month The month whose day count is sought
1767 * @param int $year The year of the month whose day count is sought
1768 * @return int
1770 function days_in_month($month, $year) {
1771 return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1775 * Calculate the position in the week of a specific calendar day
1777 * @param int $day The day of the date whose position in the week is sought
1778 * @param int $month The month of the date whose position in the week is sought
1779 * @param int $year The year of the date whose position in the week is sought
1780 * @return int
1782 function dayofweek($day, $month, $year) {
1783 // I wonder if this is any different from
1784 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1785 return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1788 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1791 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1792 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1793 * sesskey string if $USER exists, or boolean false if not.
1795 * @uses $USER
1796 * @return string
1798 function sesskey() {
1799 global $USER;
1801 if(!isset($USER)) {
1802 return false;
1805 if (empty($USER->sesskey)) {
1806 $USER->sesskey = random_string(10);
1809 return $USER->sesskey;
1814 * For security purposes, this function will check that the currently
1815 * given sesskey (passed as a parameter to the script or this function)
1816 * matches that of the current user.
1818 * @param string $sesskey optionally provided sesskey
1819 * @return bool
1821 function confirm_sesskey($sesskey=NULL) {
1822 global $USER;
1824 if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1825 return true;
1828 if (empty($sesskey)) {
1829 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
1832 if (!isset($USER->sesskey)) {
1833 return false;
1836 return ($USER->sesskey === $sesskey);
1840 * Check the session key using {@link confirm_sesskey()},
1841 * and cause a fatal error if it does not match.
1843 function require_sesskey() {
1844 if (!confirm_sesskey()) {
1845 print_error('invalidsesskey');
1850 * Setup all global $CFG course variables, set locale and also themes
1851 * This function can be used on pages that do not require login instead of require_login()
1853 * @param mixed $courseorid id of the course or course object
1855 function course_setup($courseorid=0) {
1856 global $COURSE, $CFG, $SITE;
1858 /// Redefine global $COURSE if needed
1859 if (empty($courseorid)) {
1860 // no change in global $COURSE - for backwards compatibiltiy
1861 // if require_rogin() used after require_login($courseid);
1862 } else if (is_object($courseorid)) {
1863 $COURSE = clone($courseorid);
1864 } else {
1865 global $course; // used here only to prevent repeated fetching from DB - may be removed later
1866 if ($courseorid == SITEID) {
1867 $COURSE = clone($SITE);
1868 } else if (!empty($course->id) and $course->id == $courseorid) {
1869 $COURSE = clone($course);
1870 } else {
1871 if (!$COURSE = get_record('course', 'id', $courseorid)) {
1872 error('Invalid course ID');
1877 /// set locale and themes
1878 moodle_setlocale();
1879 theme_setup();
1884 * This function checks that the current user is logged in and has the
1885 * required privileges
1887 * This function checks that the current user is logged in, and optionally
1888 * whether they are allowed to be in a particular course and view a particular
1889 * course module.
1890 * If they are not logged in, then it redirects them to the site login unless
1891 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1892 * case they are automatically logged in as guests.
1893 * If $courseid is given and the user is not enrolled in that course then the
1894 * user is redirected to the course enrolment page.
1895 * If $cm is given and the coursemodule is hidden and the user is not a teacher
1896 * in the course then the user is redirected to the course home page.
1898 * @uses $CFG
1899 * @uses $SESSION
1900 * @uses $USER
1901 * @uses $FULLME
1902 * @uses SITEID
1903 * @uses $COURSE
1904 * @param mixed $courseorid id of the course or course object
1905 * @param bool $autologinguest
1906 * @param object $cm course module object
1907 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
1908 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
1909 * in order to keep redirects working properly. MDL-14495
1911 function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
1913 global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1915 /// setup global $COURSE, themes, language and locale
1916 course_setup($courseorid);
1918 /// If the user is not even logged in yet then make sure they are
1919 if (!isloggedin()) {
1920 //NOTE: $USER->site check was obsoleted by session test cookie,
1921 // $USER->confirmed test is in login/index.php
1922 if ($setwantsurltome) {
1923 $SESSION->wantsurl = $FULLME;
1925 if (!empty($_SERVER['HTTP_REFERER'])) {
1926 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
1928 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1929 $loginguest = '?loginguest=true';
1930 } else {
1931 $loginguest = '';
1933 if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
1934 redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1935 } else {
1936 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1937 redirect($wwwroot .'/login/index.php');
1939 exit;
1942 /// loginas as redirection if needed
1943 if ($COURSE->id != SITEID and !empty($USER->realuser)) {
1944 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
1945 if ($USER->loginascontext->instanceid != $COURSE->id) {
1946 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
1951 /// check whether the user should be changing password (but only if it is REALLY them)
1952 if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
1953 $userauth = get_auth_plugin($USER->auth);
1954 if ($userauth->can_change_password()) {
1955 $SESSION->wantsurl = $FULLME;
1956 if ($changeurl = $userauth->change_password_url()) {
1957 //use plugin custom url
1958 redirect($changeurl);
1959 } else {
1960 //use moodle internal method
1961 if (empty($CFG->loginhttps)) {
1962 redirect($CFG->wwwroot .'/login/change_password.php');
1963 } else {
1964 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1965 redirect($wwwroot .'/login/change_password.php');
1968 } else {
1969 print_error('nopasswordchangeforced', 'auth');
1973 /// Check that the user account is properly set up
1974 if (user_not_fully_set_up($USER)) {
1975 $SESSION->wantsurl = $FULLME;
1976 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1979 /// Make sure current IP matches the one for this session (if required)
1980 if (!empty($CFG->tracksessionip)) {
1981 if ($USER->sessionIP != md5(getremoteaddr())) {
1982 print_error('sessionipnomatch', 'error');
1986 /// Make sure the USER has a sesskey set up. Used for checking script parameters.
1987 sesskey();
1989 // Check that the user has agreed to a site policy if there is one
1990 if (!empty($CFG->sitepolicy)) {
1991 if (!$USER->policyagreed) {
1992 $SESSION->wantsurl = $FULLME;
1993 redirect($CFG->wwwroot .'/user/policy.php');
1997 // Fetch the system context, we are going to use it a lot.
1998 $sysctx = get_context_instance(CONTEXT_SYSTEM);
2000 /// If the site is currently under maintenance, then print a message
2001 if (!has_capability('moodle/site:config', $sysctx)) {
2002 if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
2003 print_maintenance_message();
2004 exit;
2008 /// groupmembersonly access control
2009 if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2010 if (isguestuser() or !groups_has_membership($cm)) {
2011 print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
2015 // Fetch the course context, and prefetch its child contexts
2016 if (!isset($COURSE->context)) {
2017 if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
2018 print_error('nocontext');
2021 if (!empty($cm) && !isset($cm->context)) {
2022 if ( ! $cm->context = get_context_instance(CONTEXT_MODULE, $cm->id) ) {
2023 print_error('nocontext');
2026 if ($COURSE->id == SITEID) {
2027 /// Eliminate hidden site activities straight away
2028 if (!empty($cm) && !$cm->visible
2029 && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2030 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
2032 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2033 return;
2035 } else {
2037 /// Check if the user can be in a particular course
2038 if (empty($USER->access['rsw'][$COURSE->context->path])) {
2040 // MDL-13900 - If the course or the parent category are hidden
2041 // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
2043 if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
2044 !has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
2045 print_header_simple();
2046 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2050 /// Non-guests who don't currently have access, check if they can be allowed in as a guest
2052 if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
2053 if ($COURSE->guest == 1) {
2054 // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
2055 $USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
2059 /// If the user is a guest then treat them according to the course policy about guests
2061 if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
2062 if (has_capability('moodle/site:doanything', $sysctx)) {
2063 // administrators must be able to access any course - even if somebody gives them guest access
2064 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2065 return;
2068 switch ($COURSE->guest) { /// Check course policy about guest access
2070 case 1: /// Guests always allowed
2071 if (!has_capability('moodle/course:view', $COURSE->context)) { // Prohibited by capability
2072 print_header_simple();
2073 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2075 if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
2076 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
2077 get_string('activityiscurrentlyhidden'));
2080 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2081 return; // User is allowed to see this course
2083 break;
2085 case 2: /// Guests allowed with key
2086 if (!empty($USER->enrolkey[$COURSE->id])) { // Set by enrol/manual/enrol.php
2087 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2088 return true;
2090 // otherwise drop through to logic below (--> enrol.php)
2091 break;
2093 default: /// Guests not allowed
2094 $strloggedinasguest = get_string('loggedinasguest');
2095 print_header_simple('', '',
2096 build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
2097 if (empty($USER->access['rsw'][$COURSE->context->path])) { // Normal guest
2098 $loginurl = "$CFG->wwwroot/login/index.php";
2099 if (!empty($CFG->loginhttps)) {
2100 $loginurl = str_replace('http:','https:', $loginurl);
2102 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), $loginurl);
2103 } else {
2104 notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
2105 echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
2106 print_footer($COURSE);
2107 exit;
2109 break;
2112 /// For non-guests, check if they have course view access
2114 } else if (has_capability('moodle/course:view', $COURSE->context)) {
2115 if (!empty($USER->realuser)) { // Make sure the REAL person can also access this course
2116 if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
2117 print_header_simple();
2118 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2122 /// Make sure they can read this activity too, if specified
2124 if (!empty($cm) && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2125 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
2127 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2128 return; // User is allowed to see this course
2133 /// Currently not enrolled in the course, so see if they want to enrol
2134 $SESSION->wantsurl = $FULLME;
2135 redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
2136 die;
2143 * This function just makes sure a user is logged out.
2145 * @uses $CFG
2146 * @uses $USER
2148 function require_logout() {
2150 global $USER, $CFG, $SESSION;
2152 if (isloggedin()) {
2153 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2155 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2156 foreach($authsequence as $authname) {
2157 $authplugin = get_auth_plugin($authname);
2158 $authplugin->prelogout_hook();
2162 if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
2163 // This method is just to try to avoid silly warnings from PHP 4.3.0
2164 session_unregister("USER");
2165 session_unregister("SESSION");
2168 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
2169 $file = $line = null;
2170 if (headers_sent($file, $line)) {
2171 error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
2172 error_log('Headers were already sent in file: '.$file.' on line '.$line);
2173 } else {
2174 if (check_php_version('5.2.0')) {
2175 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
2176 } else {
2177 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2181 unset($_SESSION['USER']);
2182 unset($_SESSION['SESSION']);
2184 unset($SESSION);
2185 unset($USER);
2190 * This is a weaker version of {@link require_login()} which only requires login
2191 * when called from within a course rather than the site page, unless
2192 * the forcelogin option is turned on.
2194 * @uses $CFG
2195 * @param mixed $courseorid The course object or id in question
2196 * @param bool $autologinguest Allow autologin guests if that is wanted
2197 * @param object $cm Course activity module if known
2198 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2199 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2200 * in order to keep redirects working properly. MDL-14495
2202 function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
2203 global $CFG;
2204 if (!empty($CFG->forcelogin)) {
2205 // login required for both SITE and courses
2206 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2208 } else if (!empty($cm) and !$cm->visible) {
2209 // always login for hidden activities
2210 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2212 } else if ((is_object($courseorid) and $courseorid->id == SITEID)
2213 or (!is_object($courseorid) and $courseorid == SITEID)) {
2214 //login for SITE not required
2215 if ($cm and empty($cm->visible)) {
2216 // hidden activities are not accessible without login
2217 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2218 } else if ($cm and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
2219 // not-logged-in users do not have any group membership
2220 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2221 } else {
2222 user_accesstime_log(SITEID);
2223 return;
2226 } else {
2227 // course login always required
2228 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2233 * Require key login. Function terminates with error if key not found or incorrect.
2234 * @param string $script unique script identifier
2235 * @param int $instance optional instance id
2237 function require_user_key_login($script, $instance=null) {
2238 global $nomoodlecookie, $USER, $SESSION, $CFG;
2240 if (empty($nomoodlecookie)) {
2241 error('Incorrect use of require_key_login() - session cookies must be disabled!');
2244 /// extra safety
2245 @session_write_close();
2247 $keyvalue = required_param('key', PARAM_ALPHANUM);
2249 if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
2250 error('Incorrect key');
2253 if (!empty($key->validuntil) and $key->validuntil < time()) {
2254 error('Expired key');
2257 if ($key->iprestriction) {
2258 $remoteaddr = getremoteaddr();
2259 if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
2260 error('Client IP address mismatch');
2264 if (!$user = get_record('user', 'id', $key->userid)) {
2265 error('Incorrect user record');
2268 /// emulate normal session
2269 $SESSION = new object();
2270 $USER = $user;
2272 /// note we are not using normal login
2273 if (!defined('USER_KEY_LOGIN')) {
2274 define('USER_KEY_LOGIN', true);
2277 load_all_capabilities();
2279 /// return isntance id - it might be empty
2280 return $key->instance;
2284 * Creates a new private user access key.
2285 * @param string $script unique target identifier
2286 * @param int $userid
2287 * @param instance $int optional instance id
2288 * @param string $iprestriction optional ip restricted access
2289 * @param timestamp $validuntil key valid only until given data
2290 * @return string access key value
2292 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2293 $key = new object();
2294 $key->script = $script;
2295 $key->userid = $userid;
2296 $key->instance = $instance;
2297 $key->iprestriction = $iprestriction;
2298 $key->validuntil = $validuntil;
2299 $key->timecreated = time();
2301 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
2302 while (record_exists('user_private_key', 'value', $key->value)) {
2303 // must be unique
2304 $key->value = md5($userid.'_'.time().random_string(40));
2307 if (!insert_record('user_private_key', $key)) {
2308 error('Can not insert new key');
2311 return $key->value;
2315 * Modify the user table by setting the currently logged in user's
2316 * last login to now.
2318 * @uses $USER
2319 * @return bool
2321 function update_user_login_times() {
2322 global $USER;
2324 $user = new object();
2325 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
2326 $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
2328 $user->id = $USER->id;
2330 return update_record('user', $user);
2334 * Determines if a user has completed setting up their account.
2336 * @param user $user A {@link $USER} object to test for the existance of a valid name and email
2337 * @return bool
2339 function user_not_fully_set_up($user) {
2340 return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
2343 function over_bounce_threshold($user) {
2345 global $CFG;
2347 if (empty($CFG->handlebounces)) {
2348 return false;
2351 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2352 return false;
2355 // set sensible defaults
2356 if (empty($CFG->minbounces)) {
2357 $CFG->minbounces = 10;
2359 if (empty($CFG->bounceratio)) {
2360 $CFG->bounceratio = .20;
2362 $bouncecount = 0;
2363 $sendcount = 0;
2364 if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2365 $bouncecount = $bounce->value;
2367 if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2368 $sendcount = $send->value;
2370 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
2374 * @param $user - object containing an id
2375 * @param $reset - will reset the count to 0
2377 function set_send_count($user,$reset=false) {
2379 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2380 return;
2383 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2384 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2385 update_record('user_preferences',$pref);
2387 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2388 // make a new one
2389 $pref->name = 'email_send_count';
2390 $pref->value = 1;
2391 $pref->userid = $user->id;
2392 insert_record('user_preferences',$pref, false);
2397 * @param $user - object containing an id
2398 * @param $reset - will reset the count to 0
2400 function set_bounce_count($user,$reset=false) {
2401 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2402 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2403 update_record('user_preferences',$pref);
2405 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2406 // make a new one
2407 $pref->name = 'email_bounce_count';
2408 $pref->value = 1;
2409 $pref->userid = $user->id;
2410 insert_record('user_preferences',$pref, false);
2415 * Keeps track of login attempts
2417 * @uses $SESSION
2419 function update_login_count() {
2421 global $SESSION;
2423 $max_logins = 10;
2425 if (empty($SESSION->logincount)) {
2426 $SESSION->logincount = 1;
2427 } else {
2428 $SESSION->logincount++;
2431 if ($SESSION->logincount > $max_logins) {
2432 unset($SESSION->wantsurl);
2433 print_error('errortoomanylogins');
2438 * Resets login attempts
2440 * @uses $SESSION
2442 function reset_login_count() {
2443 global $SESSION;
2445 $SESSION->logincount = 0;
2448 function sync_metacourses() {
2450 global $CFG;
2452 if (!$courses = get_records('course', 'metacourse', 1)) {
2453 return;
2456 foreach ($courses as $course) {
2457 sync_metacourse($course);
2462 * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2464 * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2466 function sync_metacourse($course) {
2467 global $CFG;
2469 // Check the course is valid.
2470 if (!is_object($course)) {
2471 if (!$course = get_record('course', 'id', $course)) {
2472 return false; // invalid course id
2476 // Check that we actually have a metacourse.
2477 if (empty($course->metacourse)) {
2478 return false;
2481 // Get a list of roles that should not be synced.
2482 if (!empty($CFG->nonmetacoursesyncroleids)) {
2483 $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2484 } else {
2485 $roleexclusions = '';
2488 // Get the context of the metacourse.
2489 $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2491 // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2492 if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2493 $managers = array_keys($users);
2494 } else {
2495 $managers = array();
2498 // Get assignments of a user to a role that exist in a child course, but
2499 // not in the meta coure. That is, get a list of the assignments that need to be made.
2500 if (!$assignments = get_records_sql("
2501 SELECT
2502 ra.id, ra.roleid, ra.userid, ra.hidden
2503 FROM
2504 {$CFG->prefix}role_assignments ra,
2505 {$CFG->prefix}context con,
2506 {$CFG->prefix}course_meta cm
2507 WHERE
2508 ra.contextid = con.id AND
2509 con.contextlevel = " . CONTEXT_COURSE . " AND
2510 con.instanceid = cm.child_course AND
2511 cm.parent_course = {$course->id} AND
2512 $roleexclusions
2513 NOT EXISTS (
2514 SELECT 1 FROM
2515 {$CFG->prefix}role_assignments ra2
2516 WHERE
2517 ra2.userid = ra.userid AND
2518 ra2.roleid = ra.roleid AND
2519 ra2.contextid = {$context->id}
2521 ")) {
2522 $assignments = array();
2525 // Get assignments of a user to a role that exist in the meta course, but
2526 // not in any child courses. That is, get a list of the unassignments that need to be made.
2527 if (!$unassignments = get_records_sql("
2528 SELECT
2529 ra.id, ra.roleid, ra.userid
2530 FROM
2531 {$CFG->prefix}role_assignments ra
2532 WHERE
2533 ra.contextid = {$context->id} AND
2534 $roleexclusions
2535 NOT EXISTS (
2536 SELECT 1 FROM
2537 {$CFG->prefix}role_assignments ra2,
2538 {$CFG->prefix}context con2,
2539 {$CFG->prefix}course_meta cm
2540 WHERE
2541 ra2.userid = ra.userid AND
2542 ra2.roleid = ra.roleid AND
2543 ra2.contextid = con2.id AND
2544 con2.contextlevel = " . CONTEXT_COURSE . " AND
2545 con2.instanceid = cm.child_course AND
2546 cm.parent_course = {$course->id}
2548 ")) {
2549 $unassignments = array();
2552 $success = true;
2554 // Make the unassignments, if they are not managers.
2555 foreach ($unassignments as $unassignment) {
2556 if (!in_array($unassignment->userid, $managers)) {
2557 $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2561 // Make the assignments.
2562 foreach ($assignments as $assignment) {
2563 $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id, 0, 0, $assignment->hidden) && $success;
2566 return $success;
2568 // TODO: finish timeend and timestart
2569 // maybe we could rely on cron job to do the cleaning from time to time
2573 * Adds a record to the metacourse table and calls sync_metacoures
2575 function add_to_metacourse ($metacourseid, $courseid) {
2577 if (!$metacourse = get_record("course","id",$metacourseid)) {
2578 return false;
2581 if (!$course = get_record("course","id",$courseid)) {
2582 return false;
2585 if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2586 $rec = new object();
2587 $rec->parent_course = $metacourseid;
2588 $rec->child_course = $courseid;
2589 if (!insert_record('course_meta',$rec)) {
2590 return false;
2592 return sync_metacourse($metacourseid);
2594 return true;
2599 * Removes the record from the metacourse table and calls sync_metacourse
2601 function remove_from_metacourse($metacourseid, $courseid) {
2603 if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2604 return sync_metacourse($metacourseid);
2606 return false;
2611 * Determines if a user is currently logged in
2613 * @uses $USER
2614 * @return bool
2616 function isloggedin() {
2617 global $USER;
2619 return (!empty($USER->id));
2623 * Determines if a user is logged in as real guest user with username 'guest'.
2624 * This function is similar to original isguest() in 1.6 and earlier.
2625 * Current isguest() is deprecated - do not use it anymore.
2627 * @param $user mixed user object or id, $USER if not specified
2628 * @return bool true if user is the real guest user, false if not logged in or other user
2630 function isguestuser($user=NULL) {
2631 global $USER, $CFG;
2632 if ($user === NULL) {
2633 $user = $USER;
2634 } else if (is_numeric($user)) {
2635 $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2638 if (empty($user->id)) {
2639 return false; // not logged in, can not be guest
2642 return ($user->username == 'guest' and $user->mnethostid == $CFG->mnet_localhost_id);
2646 * Determines if the currently logged in user is in editing mode.
2647 * Note: originally this function had $userid parameter - it was not usable anyway
2649 * @uses $USER, $PAGE
2650 * @return bool
2652 function isediting() {
2653 global $USER, $PAGE;
2655 if (empty($USER->editing)) {
2656 return false;
2657 } elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
2658 return $PAGE->user_allowed_editing();
2660 return true;//false;
2664 * Determines if the logged in user is currently moving an activity
2666 * @uses $USER
2667 * @param int $courseid The id of the course being tested
2668 * @return bool
2670 function ismoving($courseid) {
2671 global $USER;
2673 if (!empty($USER->activitycopy)) {
2674 return ($USER->activitycopycourse == $courseid);
2676 return false;
2680 * Given an object containing firstname and lastname
2681 * values, this function returns a string with the
2682 * full name of the person.
2683 * The result may depend on system settings
2684 * or language. 'override' will force both names
2685 * to be used even if system settings specify one.
2687 * @uses $CFG
2688 * @uses $SESSION
2689 * @param object $user A {@link $USER} object to get full name of
2690 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2692 function fullname($user, $override=false) {
2693 global $CFG, $SESSION;
2695 if (!isset($user->firstname) and !isset($user->lastname)) {
2696 return '';
2699 if (!$override) {
2700 if (!empty($CFG->forcefirstname)) {
2701 $user->firstname = $CFG->forcefirstname;
2703 if (!empty($CFG->forcelastname)) {
2704 $user->lastname = $CFG->forcelastname;
2708 if (!empty($SESSION->fullnamedisplay)) {
2709 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2712 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
2713 return $user->firstname .' '. $user->lastname;
2715 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2716 return $user->lastname .' '. $user->firstname;
2718 } else if ($CFG->fullnamedisplay == 'firstname') {
2719 if ($override) {
2720 return get_string('fullnamedisplay', '', $user);
2721 } else {
2722 return $user->firstname;
2726 return get_string('fullnamedisplay', '', $user);
2730 * Sets a moodle cookie with an encrypted string
2732 * @uses $CFG
2733 * @uses DAYSECS
2734 * @uses HOURSECS
2735 * @param string $thing The string to encrypt and place in a cookie
2737 function set_moodle_cookie($thing) {
2738 global $CFG;
2740 if ($thing == 'guest') { // Ignore guest account
2741 return;
2744 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2746 $days = 60;
2747 $seconds = DAYSECS*$days;
2749 setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2750 setCookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2754 * Gets a moodle cookie with an encrypted string
2756 * @uses $CFG
2757 * @return string
2759 function get_moodle_cookie() {
2760 global $CFG;
2762 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2764 if (empty($_COOKIE[$cookiename])) {
2765 return '';
2766 } else {
2767 $thing = rc4decrypt($_COOKIE[$cookiename]);
2768 return ($thing == 'guest') ? '': $thing; // Ignore guest account
2773 * Returns whether a given authentication plugin exists.
2775 * @uses $CFG
2776 * @param string $auth Form of authentication to check for. Defaults to the
2777 * global setting in {@link $CFG}.
2778 * @return boolean Whether the plugin is available.
2780 function exists_auth_plugin($auth) {
2781 global $CFG;
2783 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2784 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2786 return false;
2790 * Checks if a given plugin is in the list of enabled authentication plugins.
2792 * @param string $auth Authentication plugin.
2793 * @return boolean Whether the plugin is enabled.
2795 function is_enabled_auth($auth) {
2796 if (empty($auth)) {
2797 return false;
2800 $enabled = get_enabled_auth_plugins();
2802 return in_array($auth, $enabled);
2806 * Returns an authentication plugin instance.
2808 * @uses $CFG
2809 * @param string $auth name of authentication plugin
2810 * @return object An instance of the required authentication plugin.
2812 function get_auth_plugin($auth) {
2813 global $CFG;
2815 // check the plugin exists first
2816 if (! exists_auth_plugin($auth)) {
2817 error("Authentication plugin '$auth' not found.");
2820 // return auth plugin instance
2821 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2822 $class = "auth_plugin_$auth";
2823 return new $class;
2827 * Returns array of active auth plugins.
2829 * @param bool $fix fix $CFG->auth if needed
2830 * @return array
2832 function get_enabled_auth_plugins($fix=false) {
2833 global $CFG;
2835 $default = array('manual', 'nologin');
2837 if (empty($CFG->auth)) {
2838 $auths = array();
2839 } else {
2840 $auths = explode(',', $CFG->auth);
2843 if ($fix) {
2844 $auths = array_unique($auths);
2845 foreach($auths as $k=>$authname) {
2846 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
2847 unset($auths[$k]);
2850 $newconfig = implode(',', $auths);
2851 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
2852 set_config('auth', $newconfig);
2856 return (array_merge($default, $auths));
2860 * Returns true if an internal authentication method is being used.
2861 * if method not specified then, global default is assumed
2863 * @uses $CFG
2864 * @param string $auth Form of authentication required
2865 * @return bool
2867 function is_internal_auth($auth) {
2868 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2869 return $authplugin->is_internal();
2873 * Returns true if the user is a 'restored' one
2875 * Used in the login process to inform the user
2876 * and allow him/her to reset the password
2878 * @uses $CFG
2879 * @param string $username username to be checked
2880 * @return bool
2882 function is_restored_user($username) {
2883 global $CFG;
2885 return record_exists('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, 'password', 'restored');
2889 * Returns an array of user fields
2891 * @uses $CFG
2892 * @uses $db
2893 * @return array User field/column names
2895 function get_user_fieldnames() {
2897 global $CFG, $db;
2899 $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2900 unset($fieldarray['ID']);
2902 return $fieldarray;
2906 * Creates the default "guest" user. Used both from
2907 * admin/index.php and login/index.php
2908 * @return mixed user object created or boolean false if the creation has failed
2910 function create_guest_record() {
2912 global $CFG;
2914 $guest = new stdClass();
2915 $guest->auth = 'manual';
2916 $guest->username = 'guest';
2917 $guest->password = hash_internal_user_password('guest');
2918 $guest->firstname = addslashes(get_string('guestuser'));
2919 $guest->lastname = ' ';
2920 $guest->email = 'root@localhost';
2921 $guest->description = addslashes(get_string('guestuserinfo'));
2922 $guest->mnethostid = $CFG->mnet_localhost_id;
2923 $guest->confirmed = 1;
2924 $guest->lang = $CFG->lang;
2925 $guest->timemodified= time();
2927 if (! $guest->id = insert_record("user", $guest)) {
2928 return false;
2931 return $guest;
2935 * Creates a bare-bones user record
2937 * @uses $CFG
2938 * @param string $username New user's username to add to record
2939 * @param string $password New user's password to add to record
2940 * @param string $auth Form of authentication required
2941 * @return object A {@link $USER} object
2942 * @todo Outline auth types and provide code example
2944 function create_user_record($username, $password, $auth='manual') {
2945 global $CFG;
2947 //just in case check text case
2948 $username = trim(moodle_strtolower($username));
2950 $authplugin = get_auth_plugin($auth);
2952 if ($newinfo = $authplugin->get_userinfo($username)) {
2953 $newinfo = truncate_userinfo($newinfo);
2954 foreach ($newinfo as $key => $value){
2955 $newuser->$key = addslashes($value);
2959 if (!empty($newuser->email)) {
2960 if (email_is_not_allowed($newuser->email)) {
2961 unset($newuser->email);
2965 if (!isset($newuser->city)) {
2966 $newuser->city = '';
2969 $newuser->auth = $auth;
2970 $newuser->username = $username;
2972 // fix for MDL-8480
2973 // user CFG lang for user if $newuser->lang is empty
2974 // or $user->lang is not an installed language
2975 $sitelangs = array_keys(get_list_of_languages());
2976 if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
2977 $newuser -> lang = $CFG->lang;
2979 $newuser->confirmed = 1;
2980 $newuser->lastip = getremoteaddr();
2981 if (empty($newuser->lastip)) {
2982 $newuser->lastip = '0.0.0.0';
2984 $newuser->timemodified = time();
2985 $newuser->mnethostid = $CFG->mnet_localhost_id;
2987 if (insert_record('user', $newuser)) {
2988 $user = get_complete_user_data('username', $newuser->username, $CFG->mnet_localhost_id);
2989 if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
2990 set_user_preference('auth_forcepasswordchange', 1, $user->id);
2992 update_internal_user_password($user, $password);
2993 return $user;
2995 return false;
2999 * Will update a local user record from an external source
3001 * @uses $CFG
3002 * @param string $username New user's username to add to record
3003 * @return user A {@link $USER} object
3005 function update_user_record($username, $unused) {
3006 global $CFG;
3008 $username = trim(moodle_strtolower($username)); /// just in case check text case
3010 $oldinfo = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, '','', 'id, username, auth');
3011 $userauth = get_auth_plugin($oldinfo->auth);
3013 if ($newinfo = $userauth->get_userinfo($username)) {
3014 $newinfo = truncate_userinfo($newinfo);
3015 foreach ($newinfo as $key => $value){
3016 if ($key === 'username' or $key === 'id' or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3017 // these fields must not be changed
3018 continue;
3020 $confval = $userauth->config->{'field_updatelocal_' . $key};
3021 $lockval = $userauth->config->{'field_lock_' . $key};
3022 if (empty($confval) || empty($lockval)) {
3023 continue;
3025 if ($confval === 'onlogin') {
3026 $value = addslashes($value);
3027 // MDL-4207 Don't overwrite modified user profile values with
3028 // empty LDAP values when 'unlocked if empty' is set. The purpose
3029 // of the setting 'unlocked if empty' is to allow the user to fill
3030 // in a value for the selected field _if LDAP is giving
3031 // nothing_ for this field. Thus it makes sense to let this value
3032 // stand in until LDAP is giving a value for this field.
3033 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3034 set_field('user', $key, $value, 'id', $oldinfo->id)
3035 || error_log("Error updating $key for $username");
3041 return get_complete_user_data('username', $username, $CFG->mnet_localhost_id);
3044 function truncate_userinfo($info) {
3045 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3046 /// which may have large fields
3048 // define the limits
3049 $limit = array(
3050 'username' => 100,
3051 'idnumber' => 255,
3052 'firstname' => 100,
3053 'lastname' => 100,
3054 'email' => 100,
3055 'icq' => 15,
3056 'phone1' => 20,
3057 'phone2' => 20,
3058 'institution' => 40,
3059 'department' => 30,
3060 'address' => 70,
3061 'city' => 20,
3062 'country' => 2,
3063 'url' => 255,
3066 // apply where needed
3067 $textlib = textlib_get_instance();
3068 foreach (array_keys($info) as $key) {
3069 if (!empty($limit[$key])) {
3070 $info[$key] = trim($textlib->substr($info[$key],0, $limit[$key]));
3074 return $info;
3078 * Marks user deleted in internal user database and notifies the auth plugin.
3079 * Also unenrols user from all roles and does other cleanup.
3080 * @param object $user Userobject before delete (without system magic quotes)
3081 * @return boolean success
3083 function delete_user($user) {
3084 global $CFG;
3085 require_once($CFG->libdir.'/grouplib.php');
3086 require_once($CFG->libdir.'/gradelib.php');
3087 require_once($CFG->dirroot.'/message/lib.php');
3089 begin_sql();
3091 // delete all grades - backup is kept in grade_grades_history table
3092 if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
3093 foreach ($grades as $grade) {
3094 $grade->delete('userdelete');
3098 //move unread messages from this user to read
3099 message_move_userfrom_unread2read($user->id);
3101 // remove from all groups
3102 delete_records('groups_members', 'userid', $user->id);
3104 // unenrol from all roles in all contexts
3105 role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
3107 // now do a final accesslib cleanup - removes all role assingments in user context and context itself
3108 delete_context(CONTEXT_USER, $user->id);
3110 require_once($CFG->dirroot.'/tag/lib.php');
3111 tag_set('user', $user->id, array());
3113 // workaround for bulk deletes of users with the same email address
3114 $delname = addslashes("$user->email.".time());
3115 while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
3116 $delname++;
3119 // mark internal user record as "deleted"
3120 $updateuser = new object();
3121 $updateuser->id = $user->id;
3122 $updateuser->deleted = 1;
3123 $updateuser->username = $delname; // Remember it just in case
3124 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
3125 $updateuser->idnumber = ''; // Clear this field to free it up
3126 $updateuser->timemodified = time();
3128 if (update_record('user', $updateuser)) {
3129 commit_sql();
3130 // notify auth plugin - do not block the delete even when plugin fails
3131 $authplugin = get_auth_plugin($user->auth);
3132 $authplugin->user_delete($user);
3134 events_trigger('user_deleted', $user);
3135 return true;
3137 } else {
3138 rollback_sql();
3139 return false;
3144 * Retrieve the guest user object
3146 * @uses $CFG
3147 * @return user A {@link $USER} object
3149 function guest_user() {
3150 global $CFG;
3152 if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id)) {
3153 $newuser->confirmed = 1;
3154 $newuser->lang = $CFG->lang;
3155 $newuser->lastip = getremoteaddr();
3156 if (empty($newuser->lastip)) {
3157 $newuser->lastip = '0.0.0.0';
3161 return $newuser;
3165 * Given a username and password, this function looks them
3166 * up using the currently selected authentication mechanism,
3167 * and if the authentication is successful, it returns a
3168 * valid $user object from the 'user' table.
3170 * Uses auth_ functions from the currently active auth module
3172 * After authenticate_user_login() returns success, you will need to
3173 * log that the user has logged in, and call complete_user_login() to set
3174 * the session up.
3176 * @uses $CFG
3177 * @param string $username User's username (with system magic quotes)
3178 * @param string $password User's password (with system magic quotes)
3179 * @return user|flase A {@link $USER} object or false if error
3181 function authenticate_user_login($username, $password) {
3183 global $CFG;
3185 $authsenabled = get_enabled_auth_plugins();
3187 if ($user = get_complete_user_data('username', $username)) {
3188 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
3189 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3190 add_to_log(0, 'login', 'error', 'index.php', $username);
3191 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3192 return false;
3194 $auths = array($auth);
3196 } else {
3197 // check if there's a deleted record (cheaply)
3198 if (get_field('user', 'id', 'username', $username, 'deleted', 1, '')) {
3199 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3200 return false;
3203 $auths = $authsenabled;
3204 $user = new object();
3205 $user->id = 0; // User does not exist
3208 foreach ($auths as $auth) {
3209 $authplugin = get_auth_plugin($auth);
3211 // on auth fail fall through to the next plugin
3212 if (!$authplugin->user_login($username, $password)) {
3213 continue;
3216 // successful authentication
3217 if ($user->id) { // User already exists in database
3218 if (empty($user->auth)) { // For some reason auth isn't set yet
3219 set_field('user', 'auth', $auth, 'username', $username);
3220 $user->auth = $auth;
3222 if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
3223 set_field('user','firstaccess', $user->timemodified, 'id', $user->id);
3224 $user->firstaccess = $user->timemodified;
3227 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3229 if (!$authplugin->is_internal()) { // update user record from external DB
3230 $user = update_user_record($username, get_auth_plugin($user->auth));
3232 } else {
3233 // if user not found, create him
3234 $user = create_user_record($username, $password, $auth);
3237 $authplugin->sync_roles($user);
3239 foreach ($authsenabled as $hau) {
3240 $hauth = get_auth_plugin($hau);
3241 $hauth->user_authenticated_hook($user, $username, $password);
3244 /// Log in to a second system if necessary
3245 /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
3246 if (!empty($CFG->sso)) {
3247 include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
3248 if (function_exists('sso_user_login')) {
3249 if (!sso_user_login($username, $password)) { // Perform the signon process
3250 notify('Second sign-on failed');
3255 if ($user->id===0) {
3256 return false;
3258 return $user;
3261 // failed if all the plugins have failed
3262 add_to_log(0, 'login', 'error', 'index.php', $username);
3263 if (debugging('', DEBUG_ALL)) {
3264 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3266 return false;
3270 * Call to complete the user login process after authenticate_user_login()
3271 * has succeeded. It will setup the $USER variable and other required bits
3272 * and pieces.
3274 * NOTE:
3275 * - It will NOT log anything -- up to the caller to decide what to log.
3279 * @uses $CFG, $USER
3280 * @param string $user obj
3281 * @return user|flase A {@link $USER} object or false if error
3283 function complete_user_login($user) {
3284 global $CFG, $USER;
3286 $USER = $user; // this is required because we need to access preferences here!
3288 if (!empty($CFG->regenloginsession)) {
3289 // please note this setting may break some auth plugins
3290 session_regenerate_id();
3293 reload_user_preferences();
3295 update_user_login_times();
3296 if (empty($CFG->nolastloggedin)) {
3297 set_moodle_cookie($USER->username);
3298 } else {
3299 // do not store last logged in user in cookie
3300 // auth plugins can temporarily override this from loginpage_hook()
3301 // do not save $CFG->nolastloggedin in database!
3302 set_moodle_cookie('nobody');
3304 set_login_session_preferences();
3306 // Call enrolment plugins
3307 check_enrolment_plugins($user);
3309 /// This is what lets the user do anything on the site :-)
3310 load_all_capabilities();
3312 /// Select password change url
3313 $userauth = get_auth_plugin($USER->auth);
3315 /// check whether the user should be changing password
3316 if (get_user_preferences('auth_forcepasswordchange', false)){
3317 if ($userauth->can_change_password()) {
3318 if ($changeurl = $userauth->change_password_url()) {
3319 redirect($changeurl);
3320 } else {
3321 redirect($CFG->httpswwwroot.'/login/change_password.php');
3323 } else {
3324 print_error('nopasswordchangeforced', 'auth');
3327 return $USER;
3331 * Compare password against hash stored in internal user table.
3332 * If necessary it also updates the stored hash to new format.
3334 * @param object user
3335 * @param string plain text password
3336 * @return bool is password valid?
3338 function validate_internal_user_password(&$user, $password) {
3339 global $CFG;
3341 if (!isset($CFG->passwordsaltmain)) {
3342 $CFG->passwordsaltmain = '';
3345 $validated = false;
3347 // get password original encoding in case it was not updated to unicode yet
3348 $textlib = textlib_get_instance();
3349 $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
3351 if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
3352 or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
3353 $validated = true;
3354 } else {
3355 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
3356 $alt = 'passwordsaltalt'.$i;
3357 if (!empty($CFG->$alt)) {
3358 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
3359 $validated = true;
3360 break;
3366 if ($validated) {
3367 // force update of password hash using latest main password salt and encoding if needed
3368 update_internal_user_password($user, $password);
3371 return $validated;
3375 * Calculate hashed value from password using current hash mechanism.
3377 * @param string password
3378 * @return string password hash
3380 function hash_internal_user_password($password) {
3381 global $CFG;
3383 if (isset($CFG->passwordsaltmain)) {
3384 return md5($password.$CFG->passwordsaltmain);
3385 } else {
3386 return md5($password);
3391 * Update pssword hash in user object.
3393 * @param object user
3394 * @param string plain text password
3395 * @param bool store changes also in db, default true
3396 * @return true if hash changed
3398 function update_internal_user_password(&$user, $password) {
3399 global $CFG;
3401 $authplugin = get_auth_plugin($user->auth);
3402 if ($authplugin->prevent_local_passwords()) {
3403 $hashedpassword = 'not cached';
3404 } else {
3405 $hashedpassword = hash_internal_user_password($password);
3408 return set_field('user', 'password', $hashedpassword, 'id', $user->id);
3412 * Get a complete user record, which includes all the info
3413 * in the user record
3414 * Intended for setting as $USER session variable
3416 * @uses $CFG
3417 * @uses SITEID
3418 * @param string $field The user field to be checked for a given value.
3419 * @param string $value The value to match for $field.
3420 * @return user A {@link $USER} object.
3422 function get_complete_user_data($field, $value, $mnethostid=null) {
3424 global $CFG;
3426 if (!$field || !$value) {
3427 return false;
3430 /// Build the WHERE clause for an SQL query
3432 $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
3434 // If we are loading user data based on anything other than id,
3435 // we must also restrict our search based on mnet host.
3436 if ($field != 'id') {
3437 if (empty($mnethostid)) {
3438 // if empty, we restrict to local users
3439 $mnethostid = $CFG->mnet_localhost_id;
3442 if (!empty($mnethostid)) {
3443 $mnethostid = (int)$mnethostid;
3444 $constraints .= ' AND mnethostid = ' . $mnethostid;
3447 /// Get all the basic user data
3449 if (! $user = get_record_select('user', $constraints)) {
3450 return false;
3453 /// Get various settings and preferences
3455 if ($displays = get_records('course_display', 'userid', $user->id)) {
3456 foreach ($displays as $display) {
3457 $user->display[$display->course] = $display->display;
3461 $user->preference = get_user_preferences(null, null, $user->id);
3463 $user->lastcourseaccess = array(); // during last session
3464 $user->currentcourseaccess = array(); // during current session
3465 if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
3466 foreach ($lastaccesses as $lastaccess) {
3467 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
3471 $sql = "SELECT g.id, g.courseid
3472 FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
3473 WHERE gm.groupid=g.id AND gm.userid={$user->id}";
3475 // this is a special hack to speedup calendar display
3476 $user->groupmember = array();
3477 if ($groups = get_records_sql($sql)) {
3478 foreach ($groups as $group) {
3479 if (!array_key_exists($group->courseid, $user->groupmember)) {
3480 $user->groupmember[$group->courseid] = array();
3482 $user->groupmember[$group->courseid][$group->id] = $group->id;
3486 /// Add the custom profile fields to the user record
3487 include_once($CFG->dirroot.'/user/profile/lib.php');
3488 $customfields = (array)profile_user_record($user->id);
3489 foreach ($customfields as $cname=>$cvalue) {
3490 if (!isset($user->$cname)) { // Don't overwrite any standard fields
3491 $user->$cname = $cvalue;
3495 /// Rewrite some variables if necessary
3496 if (!empty($user->description)) {
3497 $user->description = true; // No need to cart all of it around
3499 if ($user->username == 'guest') {
3500 $user->lang = $CFG->lang; // Guest language always same as site
3501 $user->firstname = get_string('guestuser'); // Name always in current language
3502 $user->lastname = ' ';
3505 if (isset($_SERVER['REMOTE_ADDR'])) {
3506 $user->sesskey = random_string(10);
3507 $user->sessionIP = md5(getremoteaddr()); // Store the current IP in the session
3510 return $user;
3514 * @uses $CFG
3515 * @param string $password the password to be checked agains the password policy
3516 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3517 * @return bool true if the password is valid according to the policy. false otherwise.
3519 function check_password_policy($password, &$errmsg) {
3520 global $CFG;
3522 if (empty($CFG->passwordpolicy)) {
3523 return true;
3526 $textlib = textlib_get_instance();
3527 $errmsg = '';
3528 if ($textlib->strlen($password) < $CFG->minpasswordlength) {
3529 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
3532 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
3533 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
3536 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
3537 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
3540 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
3541 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
3544 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
3545 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
3548 if ($errmsg == '') {
3549 return true;
3550 } else {
3551 return false;
3557 * When logging in, this function is run to set certain preferences
3558 * for the current SESSION
3560 function set_login_session_preferences() {
3561 global $SESSION, $CFG;
3563 $SESSION->justloggedin = true;
3565 unset($SESSION->lang);
3567 // Restore the calendar filters, if saved
3568 if (intval(get_user_preferences('calendar_persistflt', 0))) {
3569 include_once($CFG->dirroot.'/calendar/lib.php');
3570 calendar_set_filters_status(get_user_preferences('calendar_savedflt', 0xff));
3576 * Delete a course, including all related data from the database,
3577 * and any associated files from the moodledata folder.
3579 * @param mixed $courseorid The id of the course or course object to delete.
3580 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3581 * @return bool true if all the removals succeeded. false if there were any failures. If this
3582 * method returns false, some of the removals will probably have succeeded, and others
3583 * failed, but you have no way of knowing which.
3585 function delete_course($courseorid, $showfeedback = true) {
3586 global $CFG;
3587 $result = true;
3589 if (is_object($courseorid)) {
3590 $courseid = $courseorid->id;
3591 $course = $courseorid;
3592 } else {
3593 $courseid = $courseorid;
3594 if (!$course = get_record('course', 'id', $courseid)) {
3595 return false;
3599 // frontpage course can not be deleted!!
3600 if ($courseid == SITEID) {
3601 return false;
3604 if (!remove_course_contents($courseid, $showfeedback)) {
3605 if ($showfeedback) {
3606 notify("An error occurred while deleting some of the course contents.");
3608 $result = false;
3611 if (!delete_records("course", "id", $courseid)) {
3612 if ($showfeedback) {
3613 notify("An error occurred while deleting the main course record.");
3615 $result = false;
3618 /// Delete all roles and overiddes in the course context
3619 if (!delete_context(CONTEXT_COURSE, $courseid)) {
3620 if ($showfeedback) {
3621 notify("An error occurred while deleting the main course context.");
3623 $result = false;
3626 if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3627 if ($showfeedback) {
3628 notify("An error occurred while deleting the course files.");
3630 $result = false;
3633 if ($result) {
3634 //trigger events
3635 events_trigger('course_deleted', $course);
3638 return $result;
3642 * Clear a course out completely, deleting all content
3643 * but don't delete the course itself
3645 * @uses $CFG
3646 * @param int $courseid The id of the course that is being deleted
3647 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3648 * @return bool true if all the removals succeeded. false if there were any failures. If this
3649 * method returns false, some of the removals will probably have succeeded, and others
3650 * failed, but you have no way of knowing which.
3652 function remove_course_contents($courseid, $showfeedback=true) {
3654 global $CFG;
3655 require_once($CFG->libdir.'/questionlib.php');
3656 require_once($CFG->libdir.'/gradelib.php');
3658 $result = true;
3660 if (! $course = get_record('course', 'id', $courseid)) {
3661 error('Course ID was incorrect (can\'t find it)');
3664 $strdeleted = get_string('deleted');
3666 /// Clean up course formats (iterate through all formats in the even the course format was ever changed)
3667 $formats = get_list_of_plugins('course/format');
3668 foreach ($formats as $format) {
3669 $formatdelete = $format.'_course_format_delete_course';
3670 $formatlib = "$CFG->dirroot/course/format/$format/lib.php";
3671 if (file_exists($formatlib)) {
3672 include_once($formatlib);
3673 if (function_exists($formatdelete)) {
3674 if ($showfeedback) {
3675 notify($strdeleted.' '.$format);
3677 $formatdelete($course->id);
3682 /// Delete every instance of every module
3684 if ($allmods = get_records('modules') ) {
3685 foreach ($allmods as $mod) {
3686 $modname = $mod->name;
3687 $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3688 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
3689 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
3690 $count=0;
3691 if (file_exists($modfile)) {
3692 include_once($modfile);
3693 if (function_exists($moddelete)) {
3694 if ($instances = get_records($modname, 'course', $course->id)) {
3695 foreach ($instances as $instance) {
3696 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
3697 /// Delete activity context questions and question categories
3698 question_delete_activity($cm, $showfeedback);
3700 if ($moddelete($instance->id)) {
3701 $count++;
3703 } else {
3704 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
3705 $result = false;
3707 if ($cm) {
3708 // delete cm and its context in correct order
3709 delete_records('course_modules', 'id', $cm->id);
3710 delete_context(CONTEXT_MODULE, $cm->id);
3714 } else {
3715 notify('Function '.$moddelete.'() doesn\'t exist!');
3716 $result = false;
3719 if (function_exists($moddeletecourse)) {
3720 $moddeletecourse($course, $showfeedback);
3723 if ($showfeedback) {
3724 notify($strdeleted .' '. $count .' x '. $modname);
3727 } else {
3728 error('No modules are installed!');
3731 /// Give local code a chance to delete its references to this course.
3732 require_once($CFG->libdir.'/locallib.php');
3733 notify_local_delete_course($courseid, $showfeedback);
3735 /// Delete course blocks
3737 if ($blocks = get_records_sql("SELECT *
3738 FROM {$CFG->prefix}block_instance
3739 WHERE pagetype = '".PAGE_COURSE_VIEW."'
3740 AND pageid = $course->id")) {
3741 if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3742 if ($showfeedback) {
3743 notify($strdeleted .' block_instance');
3746 require_once($CFG->libdir.'/blocklib.php');
3747 foreach ($blocks as $block) { /// Delete any associated contexts for this block
3749 delete_context(CONTEXT_BLOCK, $block->id);
3751 // fix for MDL-7164
3752 // Get the block object and call instance_delete()
3753 if (!$record = blocks_get_record($block->blockid)) {
3754 $result = false;
3755 continue;
3757 if (!$obj = block_instance($record->name, $block)) {
3758 $result = false;
3759 continue;
3761 // Return value ignored, in core mods this does not do anything, but just in case
3762 // third party blocks might have stuff to clean up
3763 // we execute this anyway
3764 $obj->instance_delete();
3767 } else {
3768 $result = false;
3772 /// Delete any groups, removing members and grouping/course links first.
3773 require_once($CFG->dirroot.'/group/lib.php');
3774 groups_delete_groupings($courseid, $showfeedback);
3775 groups_delete_groups($courseid, $showfeedback);
3777 /// Delete all related records in other tables that may have a courseid
3778 /// This array stores the tables that need to be cleared, as
3779 /// table_name => column_name that contains the course id.
3781 $tablestoclear = array(
3782 'event' => 'courseid', // Delete events
3783 'log' => 'course', // Delete logs
3784 'course_sections' => 'course', // Delete any course stuff
3785 'course_modules' => 'course',
3786 'backup_courses' => 'courseid', // Delete scheduled backup stuff
3787 'user_lastaccess' => 'courseid',
3788 'backup_log' => 'courseid'
3790 foreach ($tablestoclear as $table => $col) {
3791 if (delete_records($table, $col, $course->id)) {
3792 if ($showfeedback) {
3793 notify($strdeleted . ' ' . $table);
3795 } else {
3796 $result = false;
3801 /// Clean up metacourse stuff
3803 if ($course->metacourse) {
3804 delete_records("course_meta","parent_course",$course->id);
3805 sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3806 if ($showfeedback) {
3807 notify("$strdeleted course_meta");
3809 } else {
3810 if ($parents = get_records("course_meta","child_course",$course->id)) {
3811 foreach ($parents as $parent) {
3812 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3814 if ($showfeedback) {
3815 notify("$strdeleted course_meta");
3820 /// Delete questions and question categories
3821 question_delete_course($course, $showfeedback);
3823 /// Remove all data from gradebook
3824 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3825 remove_course_grades($courseid, $showfeedback);
3826 remove_grade_letters($context, $showfeedback);
3828 return $result;
3832 * Change dates in module - used from course reset.
3833 * @param strin $modname forum, assignent, etc
3834 * @param array $fields array of date fields from mod table
3835 * @param int $timeshift time difference
3836 * @return success
3838 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
3839 global $CFG;
3840 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
3842 $return = true;
3843 foreach ($fields as $field) {
3844 $updatesql = "UPDATE {$CFG->prefix}$modname
3845 SET $field = $field + ($timeshift)
3846 WHERE course=$courseid AND $field<>0 AND $field<>0";
3847 $return = execute_sql($updatesql, false) && $return;
3850 $refreshfunction = $modname.'_refresh_events';
3851 if (function_exists($refreshfunction)) {
3852 $refreshfunction($courseid);
3855 return $return;
3859 * This function will empty a course of user data.
3860 * It will retain the activities and the structure of the course.
3861 * @param object $data an object containing all the settings including courseid (without magic quotes)
3862 * @return array status array of array component, item, error
3864 function reset_course_userdata($data) {
3865 global $CFG, $USER;
3866 require_once($CFG->libdir.'/gradelib.php');
3867 require_once($CFG->dirroot.'/group/lib.php');
3869 $data->courseid = $data->id;
3870 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3872 // calculate the time shift of dates
3873 if (!empty($data->reset_start_date)) {
3874 // time part of course startdate should be zero
3875 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
3876 } else {
3877 $data->timeshift = 0;
3880 // result array: component, item, error
3881 $status = array();
3883 // start the resetting
3884 $componentstr = get_string('general');
3886 // move the course start time
3887 if (!empty($data->reset_start_date) and $data->timeshift) {
3888 // change course start data
3889 set_field('course', 'startdate', $data->reset_start_date, 'id', $data->courseid);
3890 // update all course and group events - do not move activity events
3891 $updatesql = "UPDATE {$CFG->prefix}event
3892 SET timestart = timestart + ({$data->timeshift})
3893 WHERE courseid={$data->courseid} AND instance=0";
3894 execute_sql($updatesql, false);
3896 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3899 if (!empty($data->reset_logs)) {
3900 delete_records('log', 'course', $data->courseid);
3901 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
3904 if (!empty($data->reset_events)) {
3905 delete_records('event', 'courseid', $data->courseid);
3906 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
3909 if (!empty($data->reset_notes)) {
3910 require_once($CFG->dirroot.'/notes/lib.php');
3911 note_delete_all($data->courseid);
3912 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
3915 $componentstr = get_string('roles');
3917 if (!empty($data->reset_roles_overrides)) {
3918 $children = get_child_contexts($context);
3919 foreach ($children as $child) {
3920 delete_records('role_capabilities', 'contextid', $child->id);
3922 delete_records('role_capabilities', 'contextid', $context->id);
3923 //force refresh for logged in users
3924 mark_context_dirty($context->path);
3925 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
3928 if (!empty($data->reset_roles_local)) {
3929 $children = get_child_contexts($context);
3930 foreach ($children as $child) {
3931 role_unassign(0, 0, 0, $child->id);
3933 //force refresh for logged in users
3934 mark_context_dirty($context->path);
3935 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
3938 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
3939 $data->unenrolled = array();
3940 if (!empty($data->reset_roles)) {
3941 foreach($data->reset_roles as $roleid) {
3942 if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) {
3943 foreach ($users as $user) {
3944 role_unassign($roleid, $user->id, 0, $context->id);
3945 if (!has_capability('moodle/course:view', $context, $user->id)) {
3946 $data->unenrolled[$user->id] = $user->id;
3952 if (!empty($data->unenrolled)) {
3953 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol').' ('.count($data->unenrolled).')', 'error'=>false);
3957 $componentstr = get_string('groups');
3959 // remove all group members
3960 if (!empty($data->reset_groups_members)) {
3961 groups_delete_group_members($data->courseid);
3962 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
3965 // remove all groups
3966 if (!empty($data->reset_groups_remove)) {
3967 groups_delete_groups($data->courseid, false);
3968 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
3971 // remove all grouping members
3972 if (!empty($data->reset_groupings_members)) {
3973 groups_delete_groupings_groups($data->courseid, false);
3974 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
3977 // remove all groupings
3978 if (!empty($data->reset_groupings_remove)) {
3979 groups_delete_groupings($data->courseid, false);
3980 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
3983 // Look in every instance of every module for data to delete
3984 $unsupported_mods = array();
3985 if ($allmods = get_records('modules') ) {
3986 foreach ($allmods as $mod) {
3987 $modname = $mod->name;
3988 if (!count_records($modname, 'course', $data->courseid)) {
3989 continue; // skip mods with no instances
3991 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
3992 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
3993 if (file_exists($modfile)) {
3994 include_once($modfile);
3995 if (function_exists($moddeleteuserdata)) {
3996 $modstatus = $moddeleteuserdata($data);
3997 if (is_array($modstatus)) {
3998 $status = array_merge($status, $modstatus);
3999 } else {
4000 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
4002 } else {
4003 $unsupported_mods[] = $mod;
4005 } else {
4006 debugging('Missing lib.php in '.$modname.' module!');
4011 // mention unsupported mods
4012 if (!empty($unsupported_mods)) {
4013 foreach($unsupported_mods as $mod) {
4014 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
4019 $componentstr = get_string('gradebook', 'grades');
4020 // reset gradebook
4021 if (!empty($data->reset_gradebook_items)) {
4022 remove_course_grades($data->courseid, false);
4023 grade_grab_course_grades($data->courseid);
4024 grade_regrade_final_grades($data->courseid);
4025 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4027 } else if (!empty($data->reset_gradebook_grades)) {
4028 grade_course_reset($data->courseid);
4029 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4032 return $status;
4035 function generate_email_processing_address($modid,$modargs) {
4036 global $CFG;
4038 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4039 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
4042 function moodle_process_email($modargs,$body) {
4043 // the first char should be an unencoded letter. We'll take this as an action
4044 switch ($modargs{0}) {
4045 case 'B': { // bounce
4046 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4047 if ($user = get_record_select("user","id=$userid","id,email")) {
4048 // check the half md5 of their email
4049 $md5check = substr(md5($user->email),0,16);
4050 if ($md5check == substr($modargs, -16)) {
4051 set_bounce_count($user);
4053 // else maybe they've already changed it?
4056 break;
4057 // maybe more later?
4061 /// CORRESPONDENCE ////////////////////////////////////////////////
4064 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4065 * @param $action string 'get', 'buffer', 'close' or 'flush'
4066 * @return reference to mailer instance if 'get' used or nothing
4068 function &get_mailer($action='get') {
4069 global $CFG;
4071 static $mailer = null;
4072 static $counter = 0;
4074 if (!isset($CFG->smtpmaxbulk)) {
4075 $CFG->smtpmaxbulk = 1;
4078 if ($action == 'get') {
4079 $prevkeepalive = false;
4081 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4082 if ($counter < $CFG->smtpmaxbulk and empty($mailer->error_count)) {
4083 $counter++;
4084 // reset the mailer
4085 $mailer->Priority = 3;
4086 $mailer->CharSet = 'UTF-8'; // our default
4087 $mailer->ContentType = "text/plain";
4088 $mailer->Encoding = "8bit";
4089 $mailer->From = "root@localhost";
4090 $mailer->FromName = "Root User";
4091 $mailer->Sender = "";
4092 $mailer->Subject = "";
4093 $mailer->Body = "";
4094 $mailer->AltBody = "";
4095 $mailer->ConfirmReadingTo = "";
4097 $mailer->ClearAllRecipients();
4098 $mailer->ClearReplyTos();
4099 $mailer->ClearAttachments();
4100 $mailer->ClearCustomHeaders();
4101 return $mailer;
4104 $prevkeepalive = $mailer->SMTPKeepAlive;
4105 get_mailer('flush');
4108 include_once($CFG->libdir.'/phpmailer/class.phpmailer.php');
4109 $mailer = new phpmailer();
4111 $counter = 1;
4113 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
4114 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
4115 $mailer->CharSet = 'UTF-8';
4117 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
4118 // hmm, this is a bit hacky because LE should be private
4119 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
4120 $mailer->LE = "\r\n";
4121 } else {
4122 $mailer->LE = "\n";
4125 if ($CFG->smtphosts == 'qmail') {
4126 $mailer->IsQmail(); // use Qmail system
4128 } else if (empty($CFG->smtphosts)) {
4129 $mailer->IsMail(); // use PHP mail() = sendmail
4131 } else {
4132 $mailer->IsSMTP(); // use SMTP directly
4133 if (!empty($CFG->debugsmtp)) {
4134 $mailer->SMTPDebug = true;
4136 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
4137 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
4139 if ($CFG->smtpuser) { // Use SMTP authentication
4140 $mailer->SMTPAuth = true;
4141 $mailer->Username = $CFG->smtpuser;
4142 $mailer->Password = $CFG->smtppass;
4146 return $mailer;
4149 $nothing = null;
4151 // keep smtp session open after sending
4152 if ($action == 'buffer') {
4153 if (!empty($CFG->smtpmaxbulk)) {
4154 get_mailer('flush');
4155 $m =& get_mailer();
4156 if ($m->Mailer == 'smtp') {
4157 $m->SMTPKeepAlive = true;
4160 return $nothing;
4163 // close smtp session, but continue buffering
4164 if ($action == 'flush') {
4165 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4166 if (!empty($mailer->SMTPDebug)) {
4167 echo '<pre>'."\n";
4169 $mailer->SmtpClose();
4170 if (!empty($mailer->SMTPDebug)) {
4171 echo '</pre>';
4174 return $nothing;
4177 // close smtp session, do not buffer anymore
4178 if ($action == 'close') {
4179 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4180 get_mailer('flush');
4181 $mailer->SMTPKeepAlive = false;
4183 $mailer = null; // better force new instance
4184 return $nothing;
4189 * Send an email to a specified user
4191 * @uses $CFG
4192 * @uses $FULLME
4193 * @uses $MNETIDPJUMPURL IdentityProvider(IDP) URL user hits to jump to mnet peer.
4194 * @uses SITEID
4195 * @param user $user A {@link $USER} object
4196 * @param user $from A {@link $USER} object
4197 * @param string $subject plain text subject line of the email
4198 * @param string $messagetext plain text version of the message
4199 * @param string $messagehtml complete html version of the message (optional)
4200 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4201 * @param string $attachname the name of the file (extension indicates MIME)
4202 * @param bool $usetrueaddress determines whether $from email address should
4203 * be sent out. Will be overruled by user profile setting for maildisplay
4204 * @param int $wordwrapwidth custom word wrap width
4205 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4206 * was blocked by user and "false" if there was another sort of error.
4208 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4210 global $CFG, $FULLME, $MNETIDPJUMPURL;
4211 static $mnetjumps = array();
4213 if (empty($user) || empty($user->email)) {
4214 return false;
4217 if (!empty($user->deleted)) {
4218 // do not mail delted users
4219 return false;
4222 if (!empty($CFG->noemailever)) {
4223 // hidden setting for development sites, set in config.php if needed
4224 return true;
4227 if (!empty($CFG->divertallemailsto)) {
4228 $subject = "[DIVERTED {$user->email}] $subject";
4229 $user = clone($user);
4230 $user->email = $CFG->divertallemailsto;
4233 // skip mail to suspended users
4234 if (isset($user->auth) && $user->auth=='nologin') {
4235 return true;
4238 if (!empty($user->emailstop)) {
4239 return 'emailstop';
4242 if (over_bounce_threshold($user)) {
4243 error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
4244 return false;
4247 // If the user is a remote mnet user, parse the email text for URL to the
4248 // wwwroot and modify the url to direct the user's browser to login at their
4249 // home site (identity provider - idp) before hitting the link itself
4250 if (is_mnet_remote_user($user)) {
4251 require_once($CFG->dirroot.'/mnet/lib.php');
4252 // Form the request url to hit the idp's jump.php
4253 if (isset($mnetjumps[$user->mnethostid])) {
4254 $MNETIDPJUMPURL = $mnetjumps[$user->mnethostid];
4255 } else {
4256 $idp = mnet_get_peer_host($user->mnethostid);
4257 $idpjumppath = '/auth/mnet/jump.php';
4258 $MNETIDPJUMPURL = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
4259 $mnetjumps[$user->mnethostid] = $MNETIDPJUMPURL;
4262 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4263 'mnet_sso_apply_indirection',
4264 $messagetext);
4265 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4266 'mnet_sso_apply_indirection',
4267 $messagehtml);
4269 $mail =& get_mailer();
4271 if (!empty($mail->SMTPDebug)) {
4272 echo '<pre>' . "\n";
4275 /// We are going to use textlib services here
4276 $textlib = textlib_get_instance();
4278 $supportuser = generate_email_supportuser();
4280 // make up an email address for handling bounces
4281 if (!empty($CFG->handlebounces)) {
4282 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
4283 $mail->Sender = generate_email_processing_address(0,$modargs);
4284 } else {
4285 $mail->Sender = $supportuser->email;
4288 if (is_string($from)) { // So we can pass whatever we want if there is need
4289 $mail->From = $CFG->noreplyaddress;
4290 $mail->FromName = $from;
4291 } else if ($usetrueaddress and $from->maildisplay) {
4292 $mail->From = stripslashes($from->email);
4293 $mail->FromName = fullname($from);
4294 } else {
4295 $mail->From = $CFG->noreplyaddress;
4296 $mail->FromName = fullname($from);
4297 if (empty($replyto)) {
4298 $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
4302 if (!empty($replyto)) {
4303 $mail->AddReplyTo($replyto,$replytoname);
4306 $mail->Subject = substr(stripslashes($subject), 0, 900);
4308 $mail->AddAddress(stripslashes($user->email), fullname($user) );
4310 $mail->WordWrap = $wordwrapwidth; // set word wrap
4312 if (!empty($from->customheaders)) { // Add custom headers
4313 if (is_array($from->customheaders)) {
4314 foreach ($from->customheaders as $customheader) {
4315 $mail->AddCustomHeader($customheader);
4317 } else {
4318 $mail->AddCustomHeader($from->customheaders);
4322 if (!empty($from->priority)) {
4323 $mail->Priority = $from->priority;
4326 if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
4327 $mail->IsHTML(true);
4328 $mail->Encoding = 'quoted-printable'; // Encoding to use
4329 $mail->Body = $messagehtml;
4330 $mail->AltBody = "\n$messagetext\n";
4331 } else {
4332 $mail->IsHTML(false);
4333 $mail->Body = "\n$messagetext\n";
4336 if ($attachment && $attachname) {
4337 if (ereg( "\\.\\." ,$attachment )) { // Security check for ".." in dir path
4338 $mail->AddAddress($supportuser->email, fullname($supportuser, true) );
4339 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4340 } else {
4341 require_once($CFG->libdir.'/filelib.php');
4342 $mimetype = mimeinfo('type', $attachname);
4343 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
4349 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
4350 /// encoding to the specified one
4351 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
4352 /// Set it to site mail charset
4353 $charset = $CFG->sitemailcharset;
4354 /// Overwrite it with the user mail charset
4355 if (!empty($CFG->allowusermailcharset)) {
4356 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
4357 $charset = $useremailcharset;
4360 /// If it has changed, convert all the necessary strings
4361 $charsets = get_list_of_charsets();
4362 unset($charsets['UTF-8']);
4363 if (in_array($charset, $charsets)) {
4364 /// Save the new mail charset
4365 $mail->CharSet = $charset;
4366 /// And convert some strings
4367 $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
4368 foreach ($mail->ReplyTo as $key => $rt) { //ReplyTo Names
4369 $mail->ReplyTo[$key][1] = $textlib->convert($rt[1], 'utf-8', $mail->CharSet);
4371 $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet); //Subject
4372 foreach ($mail->to as $key => $to) {
4373 $mail->to[$key][1] = $textlib->convert($to[1], 'utf-8', $mail->CharSet); //To Names
4375 $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet); //Body
4376 $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet); //Subject
4380 if ($mail->Send()) {
4381 set_send_count($user);
4382 $mail->IsSMTP(); // use SMTP directly
4383 if (!empty($mail->SMTPDebug)) {
4384 echo '</pre>';
4386 return true;
4387 } else {
4388 mtrace('ERROR: '. $mail->ErrorInfo);
4389 add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
4390 if (!empty($mail->SMTPDebug)) {
4391 echo '</pre>';
4393 return false;
4398 * Generate a signoff for emails based on support settings
4401 function generate_email_signoff() {
4402 global $CFG;
4404 $signoff = "\n";
4405 if (!empty($CFG->supportname)) {
4406 $signoff .= $CFG->supportname."\n";
4408 if (!empty($CFG->supportemail)) {
4409 $signoff .= $CFG->supportemail."\n";
4411 if (!empty($CFG->supportpage)) {
4412 $signoff .= $CFG->supportpage."\n";
4414 return $signoff;
4418 * Generate a fake user for emails based on support settings
4421 function generate_email_supportuser() {
4423 global $CFG;
4425 static $supportuser;
4427 if (!empty($supportuser)) {
4428 return $supportuser;
4431 $supportuser = new object;
4432 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
4433 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
4434 $supportuser->lastname = '';
4435 $supportuser->maildisplay = true;
4437 return $supportuser;
4442 * Sets specified user's password and send the new password to the user via email.
4444 * @uses $CFG
4445 * @param user $user A {@link $USER} object
4446 * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
4447 * was blocked by user and "false" if there was another sort of error.
4449 function setnew_password_and_mail($user) {
4451 global $CFG;
4453 $site = get_site();
4455 $supportuser = generate_email_supportuser();
4457 $newpassword = generate_password();
4459 if (! set_field('user', 'password', hash_internal_user_password($newpassword), 'id', $user->id) ) {
4460 trigger_error('Could not set user password!');
4461 return false;
4464 $a = new object();
4465 $a->firstname = fullname($user, true);
4466 $a->sitename = format_string($site->fullname);
4467 $a->username = $user->username;
4468 $a->newpassword = $newpassword;
4469 $a->link = $CFG->wwwroot .'/login/';
4470 $a->signoff = generate_email_signoff();
4472 $message = get_string('newusernewpasswordtext', '', $a);
4474 $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
4476 return email_to_user($user, $supportuser, $subject, $message);
4481 * Resets specified user's password and send the new password to the user via email.
4483 * @uses $CFG
4484 * @param user $user A {@link $USER} object
4485 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4486 * was blocked by user and "false" if there was another sort of error.
4488 function reset_password_and_mail($user) {
4490 global $CFG;
4492 $site = get_site();
4493 $supportuser = generate_email_supportuser();
4495 $userauth = get_auth_plugin($user->auth);
4496 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
4497 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4498 return false;
4501 $newpassword = generate_password();
4503 if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
4504 error("Could not set user password!");
4507 $a = new object();
4508 $a->firstname = $user->firstname;
4509 $a->lastname = $user->lastname;
4510 $a->sitename = format_string($site->fullname);
4511 $a->username = $user->username;
4512 $a->newpassword = $newpassword;
4513 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
4514 $a->signoff = generate_email_signoff();
4516 $message = get_string('newpasswordtext', '', $a);
4518 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
4520 return email_to_user($user, $supportuser, $subject, $message);
4525 * Send email to specified user with confirmation text and activation link.
4527 * @uses $CFG
4528 * @param user $user A {@link $USER} object
4529 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4530 * was blocked by user and "false" if there was another sort of error.
4532 function send_confirmation_email($user) {
4534 global $CFG;
4536 $site = get_site();
4537 $supportuser = generate_email_supportuser();
4539 $data = new object();
4540 $data->firstname = fullname($user);
4541 $data->sitename = format_string($site->fullname);
4542 $data->admin = generate_email_signoff();
4544 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
4546 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
4547 $message = get_string('emailconfirmation', '', $data);
4548 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4550 $user->mailformat = 1; // Always send HTML version as well
4552 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4557 * send_password_change_confirmation_email.
4559 * @uses $CFG
4560 * @param user $user A {@link $USER} object
4561 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4562 * was blocked by user and "false" if there was another sort of error.
4564 function send_password_change_confirmation_email($user) {
4566 global $CFG;
4568 $site = get_site();
4569 $supportuser = generate_email_supportuser();
4571 $data = new object();
4572 $data->firstname = $user->firstname;
4573 $data->lastname = $user->lastname;
4574 $data->sitename = format_string($site->fullname);
4575 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
4576 $data->admin = generate_email_signoff();
4578 $message = get_string('emailpasswordconfirmation', '', $data);
4579 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
4581 return email_to_user($user, $supportuser, $subject, $message);
4586 * send_password_change_info.
4588 * @uses $CFG
4589 * @param user $user A {@link $USER} object
4590 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4591 * was blocked by user and "false" if there was another sort of error.
4593 function send_password_change_info($user) {
4595 global $CFG;
4597 $site = get_site();
4598 $supportuser = generate_email_supportuser();
4599 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4601 $data = new object();
4602 $data->firstname = $user->firstname;
4603 $data->lastname = $user->lastname;
4604 $data->sitename = format_string($site->fullname);
4605 $data->admin = generate_email_signoff();
4607 $userauth = get_auth_plugin($user->auth);
4609 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
4610 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4611 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4612 return email_to_user($user, $supportuser, $subject, $message);
4615 if ($userauth->can_change_password() and $userauth->change_password_url()) {
4616 // we have some external url for password changing
4617 $data->link .= $userauth->change_password_url();
4619 } else {
4620 //no way to change password, sorry
4621 $data->link = '';
4624 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
4625 $message = get_string('emailpasswordchangeinfo', '', $data);
4626 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4627 } else {
4628 $message = get_string('emailpasswordchangeinfofail', '', $data);
4629 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4632 return email_to_user($user, $supportuser, $subject, $message);
4637 * Check that an email is allowed. It returns an error message if there
4638 * was a problem.
4640 * @uses $CFG
4641 * @param string $email Content of email
4642 * @return string|false
4644 function email_is_not_allowed($email) {
4646 global $CFG;
4648 if (!empty($CFG->allowemailaddresses)) {
4649 $allowed = explode(' ', $CFG->allowemailaddresses);
4650 foreach ($allowed as $allowedpattern) {
4651 $allowedpattern = trim($allowedpattern);
4652 if (!$allowedpattern) {
4653 continue;
4655 if (strpos($allowedpattern, '.') === 0) {
4656 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
4657 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4658 return false;
4661 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
4662 return false;
4665 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
4667 } else if (!empty($CFG->denyemailaddresses)) {
4668 $denied = explode(' ', $CFG->denyemailaddresses);
4669 foreach ($denied as $deniedpattern) {
4670 $deniedpattern = trim($deniedpattern);
4671 if (!$deniedpattern) {
4672 continue;
4674 if (strpos($deniedpattern, '.') === 0) {
4675 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
4676 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4677 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4680 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
4681 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4686 return false;
4689 function email_welcome_message_to_user($course, $user=NULL) {
4690 global $CFG, $USER;
4692 if (isset($CFG->sendcoursewelcomemessage) and !$CFG->sendcoursewelcomemessage) {
4693 return;
4696 if (empty($user)) {
4697 if (!isloggedin()) {
4698 return false;
4700 $user = $USER;
4703 if (!empty($course->welcomemessage)) {
4704 $message = $course->welcomemessage;
4705 } else {
4706 $a = new Object();
4707 $a->coursename = $course->fullname;
4708 $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id";
4709 $message = get_string("welcometocoursetext", "", $a);
4712 /// If you don't want a welcome message sent, then make the message string blank.
4713 if (!empty($message)) {
4714 $subject = get_string('welcometocourse', '', format_string($course->fullname));
4716 if (! $teacher = get_teacher($course->id)) {
4717 $teacher = get_admin();
4719 email_to_user($user, $teacher, $subject, $message);
4723 /// FILE HANDLING /////////////////////////////////////////////
4727 * Makes an upload directory for a particular module.
4729 * @uses $CFG
4730 * @param int $courseid The id of the course in question - maps to id field of 'course' table.
4731 * @return string|false Returns full path to directory if successful, false if not
4733 function make_mod_upload_directory($courseid) {
4734 global $CFG;
4736 if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
4737 return false;
4740 $strreadme = get_string('readme');
4742 if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
4743 copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4744 } else {
4745 copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4747 return $moddata;
4751 * Makes a directory for a particular user.
4753 * @uses $CFG
4754 * @param int $userid The id of the user in question - maps to id field of 'user' table.
4755 * @param bool $test Whether we are only testing the return value (do not create the directory)
4756 * @return string|false Returns full path to directory if successful, false if not
4758 function make_user_directory($userid, $test=false) {
4759 global $CFG;
4761 if (is_bool($userid) || $userid < 0 || !ereg('^[0-9]{1,10}$', $userid) || $userid > 2147483647) {
4762 if (!$test) {
4763 notify("Given userid was not a valid integer! (" . gettype($userid) . " $userid)");
4765 return false;
4768 // Generate a two-level path for the userid. First level groups them by slices of 1000 users, second level is userid
4769 $level1 = floor($userid / 1000) * 1000;
4771 $userdir = "user/$level1/$userid";
4772 if ($test) {
4773 return $CFG->dataroot . '/' . $userdir;
4774 } else {
4775 return make_upload_directory($userdir);
4780 * Returns an array of full paths to user directories, indexed by their userids.
4782 * @param bool $only_non_empty Only return directories that contain files
4783 * @param bool $legacy Search for user directories in legacy location (dataroot/users/userid) instead of (dataroot/user/section/userid)
4784 * @return array An associative array: userid=>array(basedir => $basedir, userfolder => $userfolder)
4786 function get_user_directories($only_non_empty=true, $legacy=false) {
4787 global $CFG;
4789 $rootdir = $CFG->dataroot."/user";
4791 if ($legacy) {
4792 $rootdir = $CFG->dataroot."/users";
4794 $dirlist = array();
4796 //Check if directory exists
4797 if (check_dir_exists($rootdir, true)) {
4798 if ($legacy) {
4799 if ($userlist = get_directory_list($rootdir, '', true, true, false)) {
4800 foreach ($userlist as $userid) {
4801 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $userid);
4803 } else {
4804 notify("no directories found under $rootdir");
4806 } else {
4807 if ($grouplist =get_directory_list($rootdir, '', true, true, false)) { // directories will be in the form 0, 1000, 2000 etc...
4808 foreach ($grouplist as $group) {
4809 if ($userlist = get_directory_list("$rootdir/$group", '', true, true, false)) {
4810 foreach ($userlist as $userid) {
4811 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $group . '/' . $userid);
4817 } else {
4818 notify("$rootdir does not exist!");
4819 return false;
4821 return $dirlist;
4825 * Returns current name of file on disk if it exists.
4827 * @param string $newfile File to be verified
4828 * @return string Current name of file on disk if true
4830 function valid_uploaded_file($newfile) {
4831 if (empty($newfile)) {
4832 return '';
4834 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
4835 return $newfile['tmp_name'];
4836 } else {
4837 return '';
4842 * Returns the maximum size for uploading files.
4844 * There are seven possible upload limits:
4845 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
4846 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
4847 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
4848 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
4849 * 5. by the Moodle admin in $CFG->maxbytes
4850 * 6. by the teacher in the current course $course->maxbytes
4851 * 7. by the teacher for the current module, eg $assignment->maxbytes
4853 * These last two are passed to this function as arguments (in bytes).
4854 * Anything defined as 0 is ignored.
4855 * The smallest of all the non-zero numbers is returned.
4857 * @param int $sizebytes ?
4858 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4859 * @param int $modulebytes Current module ->maxbytes (in bytes)
4860 * @return int The maximum size for uploading files.
4861 * @todo Finish documenting this function
4863 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4865 if (! $filesize = ini_get('upload_max_filesize')) {
4866 $filesize = '5M';
4868 $minimumsize = get_real_size($filesize);
4870 if ($postsize = ini_get('post_max_size')) {
4871 $postsize = get_real_size($postsize);
4872 if ($postsize < $minimumsize) {
4873 $minimumsize = $postsize;
4877 if ($sitebytes and $sitebytes < $minimumsize) {
4878 $minimumsize = $sitebytes;
4881 if ($coursebytes and $coursebytes < $minimumsize) {
4882 $minimumsize = $coursebytes;
4885 if ($modulebytes and $modulebytes < $minimumsize) {
4886 $minimumsize = $modulebytes;
4889 return $minimumsize;
4893 * Related to {@link get_max_upload_file_size()} - this function returns an
4894 * array of possible sizes in an array, translated to the
4895 * local language.
4897 * @uses SORT_NUMERIC
4898 * @param int $sizebytes ?
4899 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4900 * @param int $modulebytes Current module ->maxbytes (in bytes)
4901 * @return int
4902 * @todo Finish documenting this function
4904 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4905 global $CFG;
4907 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
4908 return array();
4911 $filesize[$maxsize] = display_size($maxsize);
4913 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
4914 5242880, 10485760, 20971520, 52428800, 104857600);
4916 // Allow maxbytes to be selected if it falls outside the above boundaries
4917 if( isset($CFG->maxbytes) && !in_array($CFG->maxbytes, $sizelist) ){
4918 $sizelist[] = $CFG->maxbytes;
4921 foreach ($sizelist as $sizebytes) {
4922 if ($sizebytes < $maxsize) {
4923 $filesize[$sizebytes] = display_size($sizebytes);
4927 krsort($filesize, SORT_NUMERIC);
4929 return $filesize;
4933 * If there has been an error uploading a file, print the appropriate error message
4934 * Numerical constants used as constant definitions not added until PHP version 4.2.0
4936 * $filearray is a 1-dimensional sub-array of the $_FILES array
4937 * eg $filearray = $_FILES['userfile1']
4938 * If left empty then the first element of the $_FILES array will be used
4940 * @uses $_FILES
4941 * @param array $filearray A 1-dimensional sub-array of the $_FILES array
4942 * @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.
4943 * @return bool|string
4945 function print_file_upload_error($filearray = '', $returnerror = false) {
4947 if ($filearray == '' or !isset($filearray['error'])) {
4949 if (empty($_FILES)) return false;
4951 $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
4952 $filearray = array_shift($files); /// use first element of array
4955 switch ($filearray['error']) {
4957 case 0: // UPLOAD_ERR_OK
4958 if ($filearray['size'] > 0) {
4959 $errmessage = get_string('uploadproblem', $filearray['name']);
4960 } else {
4961 $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
4963 break;
4965 case 1: // UPLOAD_ERR_INI_SIZE
4966 $errmessage = get_string('uploadserverlimit');
4967 break;
4969 case 2: // UPLOAD_ERR_FORM_SIZE
4970 $errmessage = get_string('uploadformlimit');
4971 break;
4973 case 3: // UPLOAD_ERR_PARTIAL
4974 $errmessage = get_string('uploadpartialfile');
4975 break;
4977 case 4: // UPLOAD_ERR_NO_FILE
4978 $errmessage = get_string('uploadnofilefound');
4979 break;
4981 default:
4982 $errmessage = get_string('uploadproblem', $filearray['name']);
4985 if ($returnerror) {
4986 return $errmessage;
4987 } else {
4988 notify($errmessage);
4989 return true;
4995 * handy function to loop through an array of files and resolve any filename conflicts
4996 * both in the array of filenames and for what is already on disk.
4997 * not really compatible with the similar function in uploadlib.php
4998 * but this could be used for files/index.php for moving files around.
5001 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
5002 foreach ($files as $k => $f) {
5003 if (check_potential_filename($destination,$f,$files)) {
5004 $bits = explode('.', $f);
5005 for ($i = 1; true; $i++) {
5006 $try = sprintf($format, $bits[0], $i, $bits[1]);
5007 if (!check_potential_filename($destination,$try,$files)) {
5008 $files[$k] = $try;
5009 break;
5014 return $files;
5018 * @used by resolve_filename_collisions
5020 function check_potential_filename($destination,$filename,$files) {
5021 if (file_exists($destination.'/'.$filename)) {
5022 return true;
5024 if (count(array_keys($files,$filename)) > 1) {
5025 return true;
5027 return false;
5032 * Returns an array with all the filenames in
5033 * all subdirectories, relative to the given rootdir.
5034 * If excludefile is defined, then that file/directory is ignored
5035 * If getdirs is true, then (sub)directories are included in the output
5036 * If getfiles is true, then files are included in the output
5037 * (at least one of these must be true!)
5039 * @param string $rootdir ?
5040 * @param string $excludefile If defined then the specified file/directory is ignored
5041 * @param bool $descend ?
5042 * @param bool $getdirs If true then (sub)directories are included in the output
5043 * @param bool $getfiles If true then files are included in the output
5044 * @return array An array with all the filenames in
5045 * all subdirectories, relative to the given rootdir
5046 * @todo Finish documenting this function. Add examples of $excludefile usage.
5048 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5050 $dirs = array();
5052 if (!$getdirs and !$getfiles) { // Nothing to show
5053 return $dirs;
5056 if (!is_dir($rootdir)) { // Must be a directory
5057 return $dirs;
5060 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5061 return $dirs;
5064 if (!is_array($excludefiles)) {
5065 $excludefiles = array($excludefiles);
5068 while (false !== ($file = readdir($dir))) {
5069 $firstchar = substr($file, 0, 1);
5070 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5071 continue;
5073 $fullfile = $rootdir .'/'. $file;
5074 if (filetype($fullfile) == 'dir') {
5075 if ($getdirs) {
5076 $dirs[] = $file;
5078 if ($descend) {
5079 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5080 foreach ($subdirs as $subdir) {
5081 $dirs[] = $file .'/'. $subdir;
5084 } else if ($getfiles) {
5085 $dirs[] = $file;
5088 closedir($dir);
5090 asort($dirs);
5092 return $dirs;
5097 * Adds up all the files in a directory and works out the size.
5099 * @param string $rootdir ?
5100 * @param string $excludefile ?
5101 * @return array
5102 * @todo Finish documenting this function
5104 function get_directory_size($rootdir, $excludefile='') {
5106 global $CFG;
5108 // do it this way if we can, it's much faster
5109 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5110 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5111 $output = null;
5112 $return = null;
5113 exec($command,$output,$return);
5114 if (is_array($output)) {
5115 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5119 if (!is_dir($rootdir)) { // Must be a directory
5120 return 0;
5123 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
5124 return 0;
5127 $size = 0;
5129 while (false !== ($file = readdir($dir))) {
5130 $firstchar = substr($file, 0, 1);
5131 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5132 continue;
5134 $fullfile = $rootdir .'/'. $file;
5135 if (filetype($fullfile) == 'dir') {
5136 $size += get_directory_size($fullfile, $excludefile);
5137 } else {
5138 $size += filesize($fullfile);
5141 closedir($dir);
5143 return $size;
5147 * Converts bytes into display form
5149 * @param string $size ?
5150 * @return string
5151 * @staticvar string $gb Localized string for size in gigabytes
5152 * @staticvar string $mb Localized string for size in megabytes
5153 * @staticvar string $kb Localized string for size in kilobytes
5154 * @staticvar string $b Localized string for size in bytes
5155 * @todo Finish documenting this function. Verify return type.
5157 function display_size($size) {
5159 static $gb, $mb, $kb, $b;
5161 if (empty($gb)) {
5162 $gb = get_string('sizegb');
5163 $mb = get_string('sizemb');
5164 $kb = get_string('sizekb');
5165 $b = get_string('sizeb');
5168 if ($size >= 1073741824) {
5169 $size = round($size / 1073741824 * 10) / 10 . $gb;
5170 } else if ($size >= 1048576) {
5171 $size = round($size / 1048576 * 10) / 10 . $mb;
5172 } else if ($size >= 1024) {
5173 $size = round($size / 1024 * 10) / 10 . $kb;
5174 } else {
5175 $size = $size .' '. $b;
5177 return $size;
5181 * Cleans a given filename by removing suspicious or troublesome characters
5182 * Only these are allowed: alphanumeric _ - .
5183 * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
5185 * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
5186 * because native zip binaries do weird character conversions. Use PHP zipping instead.
5188 * @param string $string file name
5189 * @return string cleaned file name
5191 function clean_filename($string) {
5192 global $CFG;
5193 if (empty($CFG->unicodecleanfilename)) {
5194 $textlib = textlib_get_instance();
5195 $string = $textlib->specialtoascii($string);
5196 $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
5197 } else {
5198 //clean only ascii range
5199 $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
5201 $string = preg_replace("/_+/", '_', $string);
5202 $string = preg_replace("/\.\.+/", '.', $string);
5203 return $string;
5207 /// STRING TRANSLATION ////////////////////////////////////////
5210 * Returns the code for the current language
5212 * @uses $CFG
5213 * @param $USER
5214 * @param $SESSION
5215 * @return string
5217 function current_language() {
5218 global $CFG, $USER, $SESSION, $COURSE;
5220 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
5221 $return = $COURSE->lang;
5223 } else if (!empty($SESSION->lang)) { // Session language can override other settings
5224 $return = $SESSION->lang;
5226 } else if (!empty($USER->lang)) {
5227 $return = $USER->lang;
5229 } else {
5230 $return = $CFG->lang;
5233 if ($return == 'en') {
5234 $return = 'en_utf8';
5237 return $return;
5241 * Prints out a translated string.
5243 * Prints out a translated string using the return value from the {@link get_string()} function.
5245 * Example usage of this function when the string is in the moodle.php file:<br/>
5246 * <code>
5247 * echo '<strong>';
5248 * print_string('wordforstudent');
5249 * echo '</strong>';
5250 * </code>
5252 * Example usage of this function when the string is not in the moodle.php file:<br/>
5253 * <code>
5254 * echo '<h1>';
5255 * print_string('typecourse', 'calendar');
5256 * echo '</h1>';
5257 * </code>
5259 * @param string $identifier The key identifier for the localized string
5260 * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
5261 * @param mixed $a An object, string or number that can be used
5262 * within translation strings
5264 function print_string($identifier, $module='', $a=NULL) {
5265 echo get_string($identifier, $module, $a);
5269 * fix up the optional data in get_string()/print_string() etc
5270 * ensure possible sprintf() format characters are escaped correctly
5271 * needs to handle arbitrary strings and objects
5272 * @param mixed $a An object, string or number that can be used
5273 * @return mixed the supplied parameter 'cleaned'
5275 function clean_getstring_data( $a ) {
5276 if (is_string($a)) {
5277 return str_replace( '%','%%',$a );
5279 elseif (is_object($a)) {
5280 $a_vars = get_object_vars( $a );
5281 $new_a_vars = array();
5282 foreach ($a_vars as $fname => $a_var) {
5283 $new_a_vars[$fname] = clean_getstring_data( $a_var );
5285 return (object)$new_a_vars;
5287 else {
5288 return $a;
5293 * @return array places to look for lang strings based on the prefix to the
5294 * module name. For example qtype_ in question/type. Used by get_string and
5295 * help.php.
5297 function places_to_search_for_lang_strings() {
5298 global $CFG;
5300 return array(
5301 '__exceptions' => array('moodle', 'langconfig'),
5302 'assignment_' => array('mod/assignment/type'),
5303 'auth_' => array('auth'),
5304 'block_' => array('blocks'),
5305 'datafield_' => array('mod/data/field'),
5306 'datapreset_' => array('mod/data/preset'),
5307 'enrol_' => array('enrol'),
5308 'filter_' => array('filter'),
5309 'format_' => array('course/format'),
5310 'qtype_' => array('question/type'),
5311 'report_' => array($CFG->admin.'/report', 'course/report', 'mod/quiz/report'),
5312 'resource_' => array('mod/resource/type'),
5313 'gradereport_' => array('grade/report'),
5314 'gradeimport_' => array('grade/import'),
5315 'gradeexport_' => array('grade/export'),
5316 'qformat_' => array('question/format'),
5317 'profilefield_' => array('user/profile/field'),
5318 '' => array('mod')
5323 * Returns a localized string.
5325 * Returns the translated string specified by $identifier as
5326 * for $module. Uses the same format files as STphp.
5327 * $a is an object, string or number that can be used
5328 * within translation strings
5330 * eg "hello \$a->firstname \$a->lastname"
5331 * or "hello \$a"
5333 * If you would like to directly echo the localized string use
5334 * the function {@link print_string()}
5336 * Example usage of this function involves finding the string you would
5337 * like a local equivalent of and using its identifier and module information
5338 * to retrive it.<br/>
5339 * If you open moodle/lang/en/moodle.php and look near line 1031
5340 * you will find a string to prompt a user for their word for student
5341 * <code>
5342 * $string['wordforstudent'] = 'Your word for Student';
5343 * </code>
5344 * So if you want to display the string 'Your word for student'
5345 * in any language that supports it on your site
5346 * you just need to use the identifier 'wordforstudent'
5347 * <code>
5348 * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
5350 * </code>
5351 * If the string you want is in another file you'd take a slightly
5352 * different approach. Looking in moodle/lang/en/calendar.php you find
5353 * around line 75:
5354 * <code>
5355 * $string['typecourse'] = 'Course event';
5356 * </code>
5357 * If you want to display the string "Course event" in any language
5358 * supported you would use the identifier 'typecourse' and the module 'calendar'
5359 * (because it is in the file calendar.php):
5360 * <code>
5361 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
5362 * </code>
5364 * As a last resort, should the identifier fail to map to a string
5365 * the returned string will be [[ $identifier ]]
5367 * @uses $CFG
5368 * @param string $identifier The key identifier for the localized string
5369 * @param string $module The module where the key identifier is stored, usually expressed as the filename in the language pack without the .php on the end but can also be written as mod/forum or grade/export/xls. If none is specified then moodle.php is used.
5370 * @param mixed $a An object, string or number that can be used
5371 * within translation strings
5372 * @param array $extralocations An array of strings with other locations to look for string files
5373 * @return string The localized string.
5375 function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
5377 global $CFG;
5379 /// originally these special strings were stored in moodle.php now we are only in langconfig.php
5380 $langconfigstrs = array('alphabet', 'backupnameformat', 'decsep', 'firstdayofweek', 'listsep', 'locale',
5381 'localewin', 'localewincharset', 'oldcharset',
5382 'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
5383 'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
5384 'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
5385 'thischarset', 'thisdirection', 'thislanguage', 'strftimedatetimeshort', 'thousandssep');
5387 $filetocheck = 'langconfig.php';
5388 $defaultlang = 'en_utf8';
5389 if (in_array($identifier, $langconfigstrs)) {
5390 $module = 'langconfig'; //This strings are under langconfig.php for 1.6 lang packs
5393 $lang = current_language();
5395 if ($module == '') {
5396 $module = 'moodle';
5399 /// If the "module" is actually a pathname, then automatically derive the proper module name
5400 if (strpos($module, '/') !== false) {
5401 $modulepath = split('/', $module);
5403 switch ($modulepath[0]) {
5405 case 'mod':
5406 $module = $modulepath[1];
5407 break;
5409 case 'blocks':
5410 case 'block':
5411 $module = 'block_'.$modulepath[1];
5412 break;
5414 case 'enrol':
5415 $module = 'enrol_'.$modulepath[1];
5416 break;
5418 case 'format':
5419 $module = 'format_'.$modulepath[1];
5420 break;
5422 case 'grade':
5423 $module = 'grade'.$modulepath[1].'_'.$modulepath[2];
5424 break;
5428 /// if $a happens to have % in it, double it so sprintf() doesn't break
5429 if ($a) {
5430 $a = clean_getstring_data( $a );
5433 /// Define the two or three major locations of language strings for this module
5434 $locations = array();
5436 if (!empty($extralocations)) { // Calling code has a good idea where to look
5437 if (is_array($extralocations)) {
5438 $locations += $extralocations;
5439 } else if (is_string($extralocations)) {
5440 $locations[] = $extralocations;
5441 } else {
5442 debugging('Bad lang path provided');
5446 if (isset($CFG->running_installer)) {
5447 $module = 'installer';
5448 $filetocheck = 'installer.php';
5449 $locations[] = $CFG->dirroot.'/install/lang/';
5450 $locations[] = $CFG->dataroot.'/lang/';
5451 $locations[] = $CFG->dirroot.'/lang/';
5452 $defaultlang = 'en_utf8';
5453 } else {
5454 $locations[] = $CFG->dataroot.'/lang/';
5455 $locations[] = $CFG->dirroot.'/lang/';
5458 /// Add extra places to look for strings for particular plugin types.
5459 $rules = places_to_search_for_lang_strings();
5460 $exceptions = $rules['__exceptions'];
5461 unset($rules['__exceptions']);
5463 if (!in_array($module, $exceptions)) {
5464 $dividerpos = strpos($module, '_');
5465 if ($dividerpos === false) {
5466 $type = '';
5467 $plugin = $module;
5468 } else {
5469 $type = substr($module, 0, $dividerpos + 1);
5470 $plugin = substr($module, $dividerpos + 1);
5472 if ($module == 'local') {
5473 $locations[] = $CFG->dirroot . '/local/lang/';
5474 } if (!empty($rules[$type])) {
5475 foreach ($rules[$type] as $location) {
5476 $locations[] = $CFG->dirroot . "/$location/$plugin/lang/";
5481 /// First check all the normal locations for the string in the current language
5482 $resultstring = '';
5483 foreach ($locations as $location) {
5484 $locallangfile = $location.$lang.'_local'.'/'.$module.'.php'; //first, see if there's a local file
5485 if (file_exists($locallangfile)) {
5486 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5487 if (eval($result) === FALSE) {
5488 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5490 return $resultstring;
5493 //if local directory not found, or particular string does not exist in local direcotry
5494 $langfile = $location.$lang.'/'.$module.'.php';
5495 if (file_exists($langfile)) {
5496 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5497 if (eval($result) === FALSE) {
5498 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5500 return $resultstring;
5505 /// If the preferred language was English (utf8) we can abort now
5506 /// saving some checks beacuse it's the only "root" lang
5507 if ($lang == 'en_utf8') {
5508 return '[['. $identifier .']]';
5511 /// Is a parent language defined? If so, try to find this string in a parent language file
5513 foreach ($locations as $location) {
5514 $langfile = $location.$lang.'/'.$filetocheck;
5515 if (file_exists($langfile)) {
5516 if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
5517 if (eval($result) === FALSE) {
5518 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5520 if (!empty($parentlang)) { // found it!
5522 //first, see if there's a local file for parent
5523 $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
5524 if (file_exists($locallangfile)) {
5525 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5526 if (eval($result) === FALSE) {
5527 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5529 return $resultstring;
5533 //if local directory not found, or particular string does not exist in local direcotry
5534 $langfile = $location.$parentlang.'/'.$module.'.php';
5535 if (file_exists($langfile)) {
5536 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5537 eval($result);
5538 return $resultstring;
5546 /// Our only remaining option is to try English
5548 foreach ($locations as $location) {
5549 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5550 if (file_exists($locallangfile)) {
5551 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5552 eval($result);
5553 return $resultstring;
5557 //if local_en not found, or string not found in local_en
5558 $langfile = $location.$defaultlang.'/'.$module.'.php';
5560 if (file_exists($langfile)) {
5561 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5562 eval($result);
5563 return $resultstring;
5568 /// And, because under 1.6 en is defined as en_utf8 child, me must try
5569 /// if it hasn't been queried before.
5570 if ($defaultlang == 'en') {
5571 $defaultlang = 'en_utf8';
5572 foreach ($locations as $location) {
5573 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5574 if (file_exists($locallangfile)) {
5575 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5576 eval($result);
5577 return $resultstring;
5581 //if local_en not found, or string not found in local_en
5582 $langfile = $location.$defaultlang.'/'.$module.'.php';
5584 if (file_exists($langfile)) {
5585 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5586 eval($result);
5587 return $resultstring;
5593 return '[['.$identifier.']]'; // Last resort
5597 * This function is only used from {@link get_string()}.
5599 * @internal Only used from get_string, not meant to be public API
5600 * @param string $identifier ?
5601 * @param string $langfile ?
5602 * @param string $destination ?
5603 * @return string|false ?
5604 * @staticvar array $strings Localized strings
5605 * @access private
5606 * @todo Finish documenting this function.
5608 function get_string_from_file($identifier, $langfile, $destination) {
5610 static $strings; // Keep the strings cached in memory.
5612 if (empty($strings[$langfile])) {
5613 $string = array();
5614 include ($langfile);
5615 $strings[$langfile] = $string;
5616 } else {
5617 $string = &$strings[$langfile];
5620 if (!isset ($string[$identifier])) {
5621 return false;
5624 return $destination .'= sprintf("'. $string[$identifier] .'");';
5628 * Converts an array of strings to their localized value.
5630 * @param array $array An array of strings
5631 * @param string $module The language module that these strings can be found in.
5632 * @return string
5634 function get_strings($array, $module='') {
5636 $string = NULL;
5637 foreach ($array as $item) {
5638 $string->$item = get_string($item, $module);
5640 return $string;
5644 * Returns a list of language codes and their full names
5645 * hides the _local files from everyone.
5646 * @param bool refreshcache force refreshing of lang cache
5647 * @param bool returnall ignore langlist, return all languages available
5648 * @return array An associative array with contents in the form of LanguageCode => LanguageName
5650 function get_list_of_languages($refreshcache=false, $returnall=false) {
5652 global $CFG;
5654 $languages = array();
5656 $filetocheck = 'langconfig.php';
5658 if (!$refreshcache && !$returnall && !empty($CFG->langcache) && file_exists($CFG->dataroot .'/cache/languages')) {
5659 /// read available langs from cache
5661 $lines = file($CFG->dataroot .'/cache/languages');
5662 foreach ($lines as $line) {
5663 $line = trim($line);
5664 if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
5665 $languages[$matches[1]] = $matches[2];
5668 unset($lines); unset($line); unset($matches);
5669 return $languages;
5672 if (!$returnall && !empty($CFG->langlist)) {
5673 /// return only languages allowed in langlist admin setting
5675 $langlist = explode(',', $CFG->langlist);
5676 // fix short lang names first - non existing langs are skipped anyway...
5677 foreach ($langlist as $lang) {
5678 if (strpos($lang, '_utf8') === false) {
5679 $langlist[] = $lang.'_utf8';
5682 // find existing langs from langlist
5683 foreach ($langlist as $lang) {
5684 $lang = trim($lang); //Just trim spaces to be a bit more permissive
5685 if (strstr($lang, '_local')!==false) {
5686 continue;
5688 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5689 $shortlang = substr($lang, 0, -5);
5690 } else {
5691 $shortlang = $lang;
5693 /// Search under dirroot/lang
5694 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5695 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5696 if (!empty($string['thislanguage'])) {
5697 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5699 unset($string);
5701 /// And moodledata/lang
5702 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5703 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5704 if (!empty($string['thislanguage'])) {
5705 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5707 unset($string);
5711 } else {
5712 /// return all languages available in system
5713 /// Fetch langs from moodle/lang directory
5714 $langdirs = get_list_of_plugins('lang');
5715 /// Fetch langs from moodledata/lang directory
5716 $langdirs2 = get_list_of_plugins('lang', '', $CFG->dataroot);
5717 /// Merge both lists of langs
5718 $langdirs = array_merge($langdirs, $langdirs2);
5719 /// Sort all
5720 asort($langdirs);
5721 /// Get some info from each lang (first from moodledata, then from moodle)
5722 foreach ($langdirs as $lang) {
5723 if (strstr($lang, '_local')!==false) {
5724 continue;
5726 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5727 $shortlang = substr($lang, 0, -5);
5728 } else {
5729 $shortlang = $lang;
5731 /// Search under moodledata/lang
5732 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5733 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5734 if (!empty($string['thislanguage'])) {
5735 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5737 unset($string);
5739 /// And dirroot/lang
5740 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5741 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5742 if (!empty($string['thislanguage'])) {
5743 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5745 unset($string);
5750 if ($refreshcache && !empty($CFG->langcache)) {
5751 if ($returnall) {
5752 // we have a list of all langs only, just delete old cache
5753 @unlink($CFG->dataroot.'/cache/languages');
5755 } else {
5756 // store the list of allowed languages
5757 if ($file = fopen($CFG->dataroot .'/cache/languages', 'w')) {
5758 foreach ($languages as $key => $value) {
5759 fwrite($file, "$key $value\n");
5761 fclose($file);
5766 return $languages;
5770 * Returns a list of charset codes. It's hardcoded, so they should be added manually
5771 * (cheking that such charset is supported by the texlib library!)
5773 * @return array And associative array with contents in the form of charset => charset
5775 function get_list_of_charsets() {
5777 $charsets = array(
5778 'EUC-JP' => 'EUC-JP',
5779 'ISO-2022-JP'=> 'ISO-2022-JP',
5780 'ISO-8859-1' => 'ISO-8859-1',
5781 'SHIFT-JIS' => 'SHIFT-JIS',
5782 'GB2312' => 'GB2312',
5783 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
5784 'UTF-8' => 'UTF-8');
5786 asort($charsets);
5788 return $charsets;
5792 * For internal use only.
5793 * @return array with two elements, the path to use and the name of the lang.
5795 function get_list_of_countries_language() {
5796 global $CFG;
5798 $lang = current_language();
5799 if (is_readable($CFG->dataroot.'/lang/'. $lang .'/countries.php')) {
5800 return array($CFG->dataroot, $lang);
5802 if (is_readable($CFG->dirroot .'/lang/'. $lang .'/countries.php')) {
5803 return array($CFG->dirroot , $lang);
5806 if ($lang == 'en_utf8') {
5807 return;
5810 $parentlang = get_string('parentlanguage');
5811 if (substr($parentlang, 0, 1) != '[') {
5812 if (is_readable($CFG->dataroot.'/lang/'. $parentlang .'/countries.php')) {
5813 return array($CFG->dataroot, $parentlang);
5815 if (is_readable($CFG->dirroot .'/lang/'. $parentlang .'/countries.php')) {
5816 return array($CFG->dirroot , $parentlang);
5819 if ($parentlang == 'en_utf8') {
5820 return;
5824 if (is_readable($CFG->dataroot.'/lang/en_utf8/countries.php')) {
5825 return array($CFG->dataroot, 'en_utf8');
5827 if (is_readable($CFG->dirroot .'/lang/en_utf8/countries.php')) {
5828 return array($CFG->dirroot , 'en_utf8');
5831 return array(null, null);
5835 * Returns a list of country names in the current language
5837 * @uses $CFG
5838 * @uses $USER
5839 * @return array
5841 function get_list_of_countries() {
5842 global $CFG;
5844 list($path, $lang) = get_list_of_countries_language();
5846 if (empty($path)) {
5847 print_error('countriesphpempty', '', '', $lang);
5850 // Load all the strings into $string.
5851 include($path . '/lang/' . $lang . '/countries.php');
5853 // See if there are local overrides to countries.php.
5854 // If so, override those elements of $string.
5855 if (is_readable($CFG->dirroot .'/lang/' . $lang . '_local/countries.php')) {
5856 include($CFG->dirroot .'/lang/' . $lang . '_local/countries.php');
5858 if (is_readable($CFG->dataroot.'/lang/' . $lang . '_local/countries.php')) {
5859 include($CFG->dataroot.'/lang/' . $lang . '_local/countries.php');
5862 if (empty($string)) {
5863 print_error('countriesphpempty', '', '', $lang);
5866 uasort($string, 'strcoll');
5867 return $string;
5871 * Returns a list of valid and compatible themes
5873 * @uses $CFG
5874 * @return array
5876 function get_list_of_themes() {
5878 global $CFG;
5880 $themes = array();
5882 if (!empty($CFG->themelist)) { // use admin's list of themes
5883 $themelist = explode(',', $CFG->themelist);
5884 } else {
5885 $themelist = get_list_of_plugins("theme");
5888 foreach ($themelist as $key => $theme) {
5889 if (!file_exists("$CFG->themedir/$theme/config.php")) { // bad folder
5890 continue;
5892 $THEME = new object(); // Note this is not the global one!! :-)
5893 include("$CFG->themedir/$theme/config.php");
5894 if (!isset($THEME->sheets)) { // Not a valid 1.5 theme
5895 continue;
5897 $themes[$theme] = $theme;
5899 asort($themes);
5901 return $themes;
5906 * Returns a list of picture names in the current or specified language
5908 * @uses $CFG
5909 * @return array
5911 function get_list_of_pixnames($lang = '') {
5912 global $CFG;
5914 if (empty($lang)) {
5915 $lang = current_language();
5918 $string = array();
5920 $path = $CFG->dirroot .'/lang/en_utf8/pix.php'; // always exists
5922 if (file_exists($CFG->dataroot .'/lang/'. $lang .'_local/pix.php')) {
5923 $path = $CFG->dataroot .'/lang/'. $lang .'_local/pix.php';
5925 } else if (file_exists($CFG->dirroot .'/lang/'. $lang .'/pix.php')) {
5926 $path = $CFG->dirroot .'/lang/'. $lang .'/pix.php';
5928 } else if (file_exists($CFG->dataroot .'/lang/'. $lang .'/pix.php')) {
5929 $path = $CFG->dataroot .'/lang/'. $lang .'/pix.php';
5931 } else if ($parentlang = get_string('parentlanguage') and $parentlang != '[[parentlanguage]]') {
5932 return get_list_of_pixnames($parentlang); //return pixnames from parent language instead
5935 include($path);
5937 return $string;
5941 * Returns a list of timezones in the current language
5943 * @uses $CFG
5944 * @return array
5946 function get_list_of_timezones() {
5947 global $CFG;
5949 static $timezones;
5951 if (!empty($timezones)) { // This function has been called recently
5952 return $timezones;
5955 $timezones = array();
5957 if ($rawtimezones = get_records_sql('SELECT MAX(id), name FROM '.$CFG->prefix.'timezone GROUP BY name')) {
5958 foreach($rawtimezones as $timezone) {
5959 if (!empty($timezone->name)) {
5960 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
5961 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
5962 $timezones[$timezone->name] = $timezone->name;
5968 asort($timezones);
5970 for ($i = -13; $i <= 13; $i += .5) {
5971 $tzstring = 'UTC';
5972 if ($i < 0) {
5973 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
5974 } else if ($i > 0) {
5975 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
5976 } else {
5977 $timezones[sprintf("%.1f", $i)] = $tzstring;
5981 return $timezones;
5985 * Returns a list of currencies in the current language
5987 * @uses $CFG
5988 * @uses $USER
5989 * @return array
5991 function get_list_of_currencies() {
5992 global $CFG, $USER;
5994 $lang = current_language();
5996 if (!file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5997 if ($parentlang = get_string('parentlanguage')) {
5998 if (file_exists($CFG->dataroot .'/lang/'. $parentlang .'/currencies.php')) {
5999 $lang = $parentlang;
6000 } else {
6001 $lang = 'en_utf8'; // currencies.php must exist in this pack
6003 } else {
6004 $lang = 'en_utf8'; // currencies.php must exist in this pack
6008 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
6009 include_once($CFG->dataroot .'/lang/'. $lang .'/currencies.php');
6010 } else { //if en_utf8 is not installed in dataroot
6011 include_once($CFG->dirroot .'/lang/'. $lang .'/currencies.php');
6014 if (!empty($string)) {
6015 asort($string);
6018 return $string;
6022 /// ENCRYPTION ////////////////////////////////////////////////
6025 * rc4encrypt
6027 * @param string $data ?
6028 * @return string
6029 * @todo Finish documenting this function
6031 function rc4encrypt($data) {
6032 $password = 'nfgjeingjk';
6033 return endecrypt($password, $data, '');
6037 * rc4decrypt
6039 * @param string $data ?
6040 * @return string
6041 * @todo Finish documenting this function
6043 function rc4decrypt($data) {
6044 $password = 'nfgjeingjk';
6045 return endecrypt($password, $data, 'de');
6049 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6051 * @param string $pwd ?
6052 * @param string $data ?
6053 * @param string $case ?
6054 * @return string
6055 * @todo Finish documenting this function
6057 function endecrypt ($pwd, $data, $case) {
6059 if ($case == 'de') {
6060 $data = urldecode($data);
6063 $key[] = '';
6064 $box[] = '';
6065 $temp_swap = '';
6066 $pwd_length = 0;
6068 $pwd_length = strlen($pwd);
6070 for ($i = 0; $i <= 255; $i++) {
6071 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
6072 $box[$i] = $i;
6075 $x = 0;
6077 for ($i = 0; $i <= 255; $i++) {
6078 $x = ($x + $box[$i] + $key[$i]) % 256;
6079 $temp_swap = $box[$i];
6080 $box[$i] = $box[$x];
6081 $box[$x] = $temp_swap;
6084 $temp = '';
6085 $k = '';
6087 $cipherby = '';
6088 $cipher = '';
6090 $a = 0;
6091 $j = 0;
6093 for ($i = 0; $i < strlen($data); $i++) {
6094 $a = ($a + 1) % 256;
6095 $j = ($j + $box[$a]) % 256;
6096 $temp = $box[$a];
6097 $box[$a] = $box[$j];
6098 $box[$j] = $temp;
6099 $k = $box[(($box[$a] + $box[$j]) % 256)];
6100 $cipherby = ord(substr($data, $i, 1)) ^ $k;
6101 $cipher .= chr($cipherby);
6104 if ($case == 'de') {
6105 $cipher = urldecode(urlencode($cipher));
6106 } else {
6107 $cipher = urlencode($cipher);
6110 return $cipher;
6114 /// CALENDAR MANAGEMENT ////////////////////////////////////////////////////////////////
6118 * Call this function to add an event to the calendar table
6119 * and to call any calendar plugins
6121 * @uses $CFG
6122 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field. The object event should include the following:
6123 * <ul>
6124 * <li><b>$event->name</b> - Name for the event
6125 * <li><b>$event->description</b> - Description of the event (defaults to '')
6126 * <li><b>$event->format</b> - Format for the description (using formatting types defined at the top of weblib.php)
6127 * <li><b>$event->courseid</b> - The id of the course this event belongs to (0 = all courses)
6128 * <li><b>$event->groupid</b> - The id of the group this event belongs to (0 = no group)
6129 * <li><b>$event->userid</b> - The id of the user this event belongs to (0 = no user)
6130 * <li><b>$event->modulename</b> - Name of the module that creates this event
6131 * <li><b>$event->instance</b> - Instance of the module that owns this event
6132 * <li><b>$event->eventtype</b> - The type info together with the module info could
6133 * be used by calendar plugins to decide how to display event
6134 * <li><b>$event->timestart</b>- Timestamp for start of event
6135 * <li><b>$event->timeduration</b> - Duration (defaults to zero)
6136 * <li><b>$event->visible</b> - 0 if the event should be hidden (e.g. because the activity that created it is hidden)
6137 * </ul>
6138 * @return int The id number of the resulting record
6140 function add_event($event) {
6142 global $CFG;
6144 $event->timemodified = time();
6146 if (!$event->id = insert_record('event', $event)) {
6147 return false;
6150 if (!empty($CFG->calendar)) { // call the add_event function of the selected calendar
6151 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6152 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6153 $calendar_add_event = $CFG->calendar.'_add_event';
6154 if (function_exists($calendar_add_event)) {
6155 $calendar_add_event($event);
6160 return $event->id;
6164 * Call this function to update an event in the calendar table
6165 * the event will be identified by the id field of the $event object.
6167 * @uses $CFG
6168 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6169 * @return bool
6171 function update_event($event) {
6173 global $CFG;
6175 $event->timemodified = time();
6177 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6178 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6179 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6180 $calendar_update_event = $CFG->calendar.'_update_event';
6181 if (function_exists($calendar_update_event)) {
6182 $calendar_update_event($event);
6186 return update_record('event', $event);
6190 * Call this function to delete the event with id $id from calendar table.
6192 * @uses $CFG
6193 * @param int $id The id of an event from the 'calendar' table.
6194 * @return array An associative array with the results from the SQL call.
6195 * @todo Verify return type
6197 function delete_event($id) {
6199 global $CFG;
6201 if (!empty($CFG->calendar)) { // call the delete_event function of the selected calendar
6202 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6203 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6204 $calendar_delete_event = $CFG->calendar.'_delete_event';
6205 if (function_exists($calendar_delete_event)) {
6206 $calendar_delete_event($id);
6210 return delete_records('event', 'id', $id);
6214 * Call this function to hide an event in the calendar table
6215 * the event will be identified by the id field of the $event object.
6217 * @uses $CFG
6218 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6219 * @return array An associative array with the results from the SQL call.
6220 * @todo Verify return type
6222 function hide_event($event) {
6223 global $CFG;
6225 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6226 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6227 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6228 $calendar_hide_event = $CFG->calendar.'_hide_event';
6229 if (function_exists($calendar_hide_event)) {
6230 $calendar_hide_event($event);
6234 return set_field('event', 'visible', 0, 'id', $event->id);
6238 * Call this function to unhide an event in the calendar table
6239 * the event will be identified by the id field of the $event object.
6241 * @uses $CFG
6242 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6243 * @return array An associative array with the results from the SQL call.
6244 * @todo Verify return type
6246 function show_event($event) {
6247 global $CFG;
6249 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6250 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6251 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6252 $calendar_show_event = $CFG->calendar.'_show_event';
6253 if (function_exists($calendar_show_event)) {
6254 $calendar_show_event($event);
6258 return set_field('event', 'visible', 1, 'id', $event->id);
6262 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6265 * Lists plugin directories within some directory
6267 * @uses $CFG
6268 * @param string $plugin dir under we'll look for plugins (defaults to 'mod')
6269 * @param string $exclude dir name to exclude from the list (defaults to none)
6270 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
6271 * @return array of plugins found under the requested parameters
6273 function get_list_of_plugins($plugin='mod', $exclude='', $basedir='') {
6275 global $CFG;
6277 $plugins = array();
6279 if (empty($basedir)) {
6281 # This switch allows us to use the appropiate theme directory - and potentialy alternatives for other plugins
6282 switch ($plugin) {
6283 case "theme":
6284 $basedir = $CFG->themedir;
6285 break;
6287 default:
6288 $basedir = $CFG->dirroot .'/'. $plugin;
6291 } else {
6292 $basedir = $basedir .'/'. $plugin;
6295 if (file_exists($basedir) && filetype($basedir) == 'dir') {
6296 $dirhandle = opendir($basedir);
6297 while (false !== ($dir = readdir($dirhandle))) {
6298 $firstchar = substr($dir, 0, 1);
6299 if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == 'simpletest' or $dir == $exclude) {
6300 continue;
6302 if (filetype($basedir .'/'. $dir) != 'dir') {
6303 continue;
6305 $plugins[] = $dir;
6307 closedir($dirhandle);
6309 if ($plugins) {
6310 asort($plugins);
6312 return $plugins;
6316 * Returns true if the current version of PHP is greater that the specified one.
6318 * @param string $version The version of php being tested.
6319 * @return bool
6321 function check_php_version($version='4.1.0') {
6322 return (version_compare(phpversion(), $version) >= 0);
6326 * Checks to see if is the browser operating system matches the specified
6327 * brand.
6329 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
6331 * @uses $_SERVER
6332 * @param string $brand The operating system identifier being tested
6333 * @return bool true if the given brand below to the detected operating system
6335 function check_browser_operating_system($brand) {
6336 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6337 return false;
6340 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
6341 return true;
6344 return false;
6348 * Checks to see if is a browser matches the specified
6349 * brand and is equal or better version.
6351 * @uses $_SERVER
6352 * @param string $brand The browser identifier being tested
6353 * @param int $version The version of the browser
6354 * @return bool true if the given version is below that of the detected browser
6356 function check_browser_version($brand='MSIE', $version=5.5) {
6357 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6358 return false;
6361 $agent = $_SERVER['HTTP_USER_AGENT'];
6363 switch ($brand) {
6365 case 'Camino': /// Mozilla Firefox browsers
6367 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
6368 if (version_compare($match[1], $version) >= 0) {
6369 return true;
6372 break;
6375 case 'Firefox': /// Mozilla Firefox browsers
6377 if (preg_match("/Firefox\/([0-9\.]+)/i", $agent, $match)) {
6378 if (version_compare($match[1], $version) >= 0) {
6379 return true;
6382 break;
6385 case 'Gecko': /// Gecko based browsers
6387 if (substr_count($agent, 'Camino')) {
6388 // MacOS X Camino support
6389 $version = 20041110;
6392 // the proper string - Gecko/CCYYMMDD Vendor/Version
6393 // Faster version and work-a-round No IDN problem.
6394 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
6395 if ($match[1] > $version) {
6396 return true;
6399 break;
6402 case 'MSIE': /// Internet Explorer
6404 if (strpos($agent, 'Opera')) { // Reject Opera
6405 return false;
6407 $string = explode(';', $agent);
6408 if (!isset($string[1])) {
6409 return false;
6411 $string = explode(' ', trim($string[1]));
6412 if (!isset($string[0]) and !isset($string[1])) {
6413 return false;
6415 if ($string[0] == $brand and (float)$string[1] >= $version ) {
6416 return true;
6418 break;
6420 case 'Opera': /// Opera
6422 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
6423 if (version_compare($match[1], $version) >= 0) {
6424 return true;
6427 break;
6429 case 'Chrome':
6430 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
6431 if (version_compare($match[1], $version) >= 0) {
6432 return true;
6435 break;
6437 case 'Safari': /// Safari
6438 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS
6439 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
6440 return false;
6441 } elseif (strpos($agent, 'Shiira')) { // Reject Shiira
6442 return false;
6443 } elseif (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
6444 return false;
6446 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
6447 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
6448 return false;
6451 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
6452 if (version_compare($match[1], $version) >= 0) {
6453 return true;
6457 break;
6461 return false;
6465 * Returns one or several CSS class names that match the user's browser. These can be put
6466 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
6468 function get_browser_version_classes() {
6469 $classes = '';
6470 if (check_browser_version("MSIE", "0")) {
6471 $classes .= 'ie ';
6472 if (check_browser_version("MSIE", 8)) {
6473 $classes .= 'ie8 ';
6474 } elseif (check_browser_version("MSIE", 7)) {
6475 $classes .= 'ie7 ';
6476 } elseif (check_browser_version("MSIE", 6)) {
6477 $classes .= 'ie6 ';
6479 } elseif (check_browser_version("Firefox", 0) || check_browser_version("Gecko", 0) || check_browser_version("Camino", 0)) {
6480 $classes .= 'gecko ';
6482 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
6483 $classes .= "gecko{$matches[1]}{$matches[2]} ";
6486 } elseif (check_browser_version("Safari", 0)) {
6487 $classes .= 'safari ';
6489 } elseif (check_browser_version("Opera", 0)) {
6490 $classes .= 'opera ';
6494 return $classes;
6498 * This function makes the return value of ini_get consistent if you are
6499 * setting server directives through the .htaccess file in apache.
6500 * Current behavior for value set from php.ini On = 1, Off = [blank]
6501 * Current behavior for value set from .htaccess On = On, Off = Off
6502 * Contributed by jdell @ unr.edu
6504 * @param string $ini_get_arg ?
6505 * @return bool
6506 * @todo Finish documenting this function
6508 function ini_get_bool($ini_get_arg) {
6509 $temp = ini_get($ini_get_arg);
6511 if ($temp == '1' or strtolower($temp) == 'on') {
6512 return true;
6514 return false;
6518 * Compatibility stub to provide backward compatibility
6520 * Determines if the HTML editor is enabled.
6521 * @deprecated Use {@link can_use_html_editor()} instead.
6523 function can_use_richtext_editor() {
6524 return can_use_html_editor();
6528 * Determines if the HTML editor is enabled.
6530 * This depends on site and user
6531 * settings, as well as the current browser being used.
6533 * @return string|false Returns false if editor is not being used, otherwise
6534 * returns 'MSIE' or 'Gecko'.
6536 function can_use_html_editor() {
6537 global $USER, $CFG;
6539 if (!empty($USER->htmleditor) and !empty($CFG->htmleditor)) {
6540 if (check_browser_version('MSIE', 5.5)) {
6541 return 'MSIE';
6542 } else if (check_browser_version('Gecko', 20030516)) {
6543 return 'Gecko';
6544 } else if (check_browser_version('Safari', 531)) {
6545 return 'AppleWebKit';
6548 return false;
6552 * Hack to find out the GD version by parsing phpinfo output
6554 * @return int GD version (1, 2, or 0)
6556 function check_gd_version() {
6557 $gdversion = 0;
6559 if (function_exists('gd_info')){
6560 $gd_info = gd_info();
6561 if (substr_count($gd_info['GD Version'], '2.')) {
6562 $gdversion = 2;
6563 } else if (substr_count($gd_info['GD Version'], '1.')) {
6564 $gdversion = 1;
6567 } else {
6568 ob_start();
6569 phpinfo(INFO_MODULES);
6570 $phpinfo = ob_get_contents();
6571 ob_end_clean();
6573 $phpinfo = explode("\n", $phpinfo);
6576 foreach ($phpinfo as $text) {
6577 $parts = explode('</td>', $text);
6578 foreach ($parts as $key => $val) {
6579 $parts[$key] = trim(strip_tags($val));
6581 if ($parts[0] == 'GD Version') {
6582 if (substr_count($parts[1], '2.0')) {
6583 $parts[1] = '2.0';
6585 $gdversion = intval($parts[1]);
6590 return $gdversion; // 1, 2 or 0
6594 * Determine if moodle installation requires update
6596 * Checks version numbers of main code and all modules to see
6597 * if there are any mismatches
6599 * @uses $CFG
6600 * @return bool
6602 function moodle_needs_upgrading() {
6603 global $CFG;
6605 $version = null;
6606 include_once($CFG->dirroot .'/version.php'); # defines $version and upgrades
6607 if ($CFG->version) {
6608 if ($version > $CFG->version) {
6609 return true;
6611 if ($mods = get_list_of_plugins('mod')) {
6612 foreach ($mods as $mod) {
6613 $fullmod = $CFG->dirroot .'/mod/'. $mod;
6614 $module = new object();
6615 if (!is_readable($fullmod .'/version.php')) {
6616 notify('Module "'. $mod .'" is not readable - check permissions');
6617 continue;
6619 include_once($fullmod .'/version.php'); # defines $module with version etc
6620 if ($currmodule = get_record('modules', 'name', $mod)) {
6621 if ($module->version > $currmodule->version) {
6622 return true;
6627 } else {
6628 return true;
6630 return false;
6634 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
6637 * Notify admin users or admin user of any failed logins (since last notification).
6639 * Note that this function must be only executed from the cron script
6640 * It uses the cache_flags system to store temporary records, deleting them
6641 * by name before finishing
6643 * @uses $CFG
6644 * @uses $db
6645 * @uses HOURSECS
6647 function notify_login_failures() {
6648 global $CFG, $db;
6650 switch ($CFG->notifyloginfailures) {
6651 case 'mainadmin' :
6652 $recip = array(get_admin());
6653 break;
6654 case 'alladmins':
6655 $recip = get_admins();
6656 break;
6659 if (empty($CFG->lastnotifyfailure)) {
6660 $CFG->lastnotifyfailure=0;
6663 // we need to deal with the threshold stuff first.
6664 if (empty($CFG->notifyloginthreshold)) {
6665 $CFG->notifyloginthreshold = 10; // default to something sensible.
6668 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
6669 /// and insert them into the cache_flags temp table
6670 $iprs = get_recordset_sql("SELECT ip, count(*)
6671 FROM {$CFG->prefix}log
6672 WHERE module = 'login'
6673 AND action = 'error'
6674 AND time > $CFG->lastnotifyfailure
6675 GROUP BY ip
6676 HAVING count(*) >= $CFG->notifyloginthreshold");
6677 while ($iprec = rs_fetch_next_record($iprs)) {
6678 if (!empty($iprec->ip)) {
6679 set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
6682 rs_close($iprs);
6684 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
6685 /// and insert them into the cache_flags temp table
6686 $infors = get_recordset_sql("SELECT info, count(*)
6687 FROM {$CFG->prefix}log
6688 WHERE module = 'login'
6689 AND action = 'error'
6690 AND time > $CFG->lastnotifyfailure
6691 GROUP BY info
6692 HAVING count(*) >= $CFG->notifyloginthreshold");
6693 while ($inforec = rs_fetch_next_record($infors)) {
6694 if (!empty($inforec->info)) {
6695 set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
6698 rs_close($infors);
6700 /// Now, select all the login error logged records belonging to the ips and infos
6701 /// since lastnotifyfailure, that we have stored in the cache_flags table
6702 $logsrs = get_recordset_sql("SELECT l.*, u.firstname, u.lastname
6703 FROM {$CFG->prefix}log l
6704 JOIN {$CFG->prefix}cache_flags cf ON (l.ip = cf.name)
6705 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6706 WHERE l.module = 'login'
6707 AND l.action = 'error'
6708 AND l.time > $CFG->lastnotifyfailure
6709 AND cf.flagtype = 'login_failure_by_ip'
6710 UNION ALL
6711 SELECT l.*, u.firstname, u.lastname
6712 FROM {$CFG->prefix}log l
6713 JOIN {$CFG->prefix}cache_flags cf ON (l.info = cf.name)
6714 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6715 WHERE l.module = 'login'
6716 AND l.action = 'error'
6717 AND l.time > $CFG->lastnotifyfailure
6718 AND cf.flagtype = 'login_failure_by_info'
6719 ORDER BY time DESC");
6721 /// Init some variables
6722 $count = 0;
6723 $messages = '';
6724 /// Iterate over the logs recordset
6725 while ($log = rs_fetch_next_record($logsrs)) {
6726 $log->time = userdate($log->time);
6727 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
6728 $count++;
6730 rs_close($logsrs);
6732 /// If we haven't run in the last hour and
6733 /// we have something useful to report and we
6734 /// are actually supposed to be reporting to somebody
6735 if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
6736 $site = get_site();
6737 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
6738 /// Calculate the complete body of notification (start + messages + end)
6739 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
6740 (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
6741 $messages .
6742 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
6744 /// For each destination, send mail
6745 mtrace('Emailing admins about '. $count .' failed login attempts');
6746 foreach ($recip as $admin) {
6747 email_to_user($admin,get_admin(), $subject, $body);
6750 /// Update lastnotifyfailure with current time
6751 set_config('lastnotifyfailure', time());
6754 /// Finally, delete all the temp records we have created in cache_flags
6755 delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
6759 * moodle_setlocale
6761 * @uses $CFG
6762 * @param string $locale ?
6763 * @todo Finish documenting this function
6765 function moodle_setlocale($locale='') {
6767 global $CFG;
6769 static $currentlocale = ''; // last locale caching
6771 $oldlocale = $currentlocale;
6773 /// Fetch the correct locale based on ostype
6774 if($CFG->ostype == 'WINDOWS') {
6775 $stringtofetch = 'localewin';
6776 } else {
6777 $stringtofetch = 'locale';
6780 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
6781 if (!empty($locale)) {
6782 $currentlocale = $locale;
6783 } else if (!empty($CFG->locale)) { // override locale for all language packs
6784 $currentlocale = $CFG->locale;
6785 } else {
6786 $currentlocale = get_string($stringtofetch);
6789 /// do nothing if locale already set up
6790 if ($oldlocale == $currentlocale) {
6791 return;
6794 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
6795 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
6796 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
6798 /// Get current values
6799 $monetary= setlocale (LC_MONETARY, 0);
6800 $numeric = setlocale (LC_NUMERIC, 0);
6801 $ctype = setlocale (LC_CTYPE, 0);
6802 if ($CFG->ostype != 'WINDOWS') {
6803 $messages= setlocale (LC_MESSAGES, 0);
6805 /// Set locale to all
6806 setlocale (LC_ALL, $currentlocale);
6807 /// Set old values
6808 setlocale (LC_MONETARY, $monetary);
6809 setlocale (LC_NUMERIC, $numeric);
6810 if ($CFG->ostype != 'WINDOWS') {
6811 setlocale (LC_MESSAGES, $messages);
6813 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
6814 setlocale (LC_CTYPE, $ctype);
6819 * Converts string to lowercase using most compatible function available.
6821 * @param string $string The string to convert to all lowercase characters.
6822 * @param string $encoding The encoding on the string.
6823 * @return string
6824 * @todo Add examples of calling this function with/without encoding types
6825 * @deprecated Use textlib->strtolower($text) instead.
6827 function moodle_strtolower ($string, $encoding='') {
6829 //If not specified use utf8
6830 if (empty($encoding)) {
6831 $encoding = 'UTF-8';
6833 //Use text services
6834 $textlib = textlib_get_instance();
6836 return $textlib->strtolower($string, $encoding);
6840 * Count words in a string.
6842 * Words are defined as things between whitespace.
6844 * @param string $string The text to be searched for words.
6845 * @return int The count of words in the specified string
6847 function count_words($string) {
6848 $string = strip_tags($string);
6849 return count(preg_split("/\w\b/", $string)) - 1;
6852 /** Count letters in a string.
6854 * Letters are defined as chars not in tags and different from whitespace.
6856 * @param string $string The text to be searched for letters.
6857 * @return int The count of letters in the specified text.
6859 function count_letters($string) {
6860 /// Loading the textlib singleton instance. We are going to need it.
6861 $textlib = textlib_get_instance();
6863 $string = strip_tags($string); // Tags are out now
6864 $string = ereg_replace('[[:space:]]*','',$string); //Whitespace are out now
6866 return $textlib->strlen($string);
6870 * Generate and return a random string of the specified length.
6872 * @param int $length The length of the string to be created.
6873 * @return string
6875 function random_string ($length=15) {
6876 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
6877 $pool .= 'abcdefghijklmnopqrstuvwxyz';
6878 $pool .= '0123456789';
6879 $poollen = strlen($pool);
6880 mt_srand ((double) microtime() * 1000000);
6881 $string = '';
6882 for ($i = 0; $i < $length; $i++) {
6883 $string .= substr($pool, (mt_rand()%($poollen)), 1);
6885 return $string;
6889 * Generate a complex random string (usefull for md5 salts)
6891 * This function is based on the above {@link random_string()} however it uses a
6892 * larger pool of characters and generates a string between 24 and 32 characters
6894 * @param int $length Optional if set generates a string to exactly this length
6895 * @return string
6897 function complex_random_string($length=null) {
6898 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
6899 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
6900 $poollen = strlen($pool);
6901 mt_srand ((double) microtime() * 1000000);
6902 if ($length===null) {
6903 $length = floor(rand(24,32));
6905 $string = '';
6906 for ($i = 0; $i < $length; $i++) {
6907 $string .= $pool[(mt_rand()%$poollen)];
6909 return $string;
6913 * Given some text (which may contain HTML) and an ideal length,
6914 * this function truncates the text neatly on a word boundary if possible
6915 * @param string $text - text to be shortened
6916 * @param int $ideal - ideal string length
6917 * @param boolean $exact if false, $text will not be cut mid-word
6918 * @return string $truncate - shortened string
6921 function shorten_text($text, $ideal=30, $exact = false) {
6923 global $CFG;
6924 $ending = '...';
6926 // if the plain text is shorter than the maximum length, return the whole text
6927 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
6928 return $text;
6931 // Splits on HTML tags. Each open/close/empty tag will be the first thing
6932 // and only tag in its 'line'
6933 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
6935 $total_length = strlen($ending);
6936 $truncate = '';
6938 // This array stores information about open and close tags and their position
6939 // in the truncated string. Each item in the array is an object with fields
6940 // ->open (true if open), ->tag (tag name in lower case), and ->pos
6941 // (byte position in truncated text)
6942 $tagdetails = array();
6944 foreach ($lines as $line_matchings) {
6945 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
6946 if (!empty($line_matchings[1])) {
6947 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
6948 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
6949 // do nothing
6950 // if tag is a closing tag (f.e. </b>)
6951 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
6952 // record closing tag
6953 $tagdetails[] = (object)array('open'=>false,
6954 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6955 // if tag is an opening tag (f.e. <b>)
6956 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
6957 // record opening tag
6958 $tagdetails[] = (object)array('open'=>true,
6959 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6961 // add html-tag to $truncate'd text
6962 $truncate .= $line_matchings[1];
6965 // calculate the length of the plain text part of the line; handle entities as one character
6966 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
6967 if ($total_length+$content_length > $ideal) {
6968 // the number of characters which are left
6969 $left = $ideal - $total_length;
6970 $entities_length = 0;
6971 // search for html entities
6972 if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
6973 // calculate the real length of all entities in the legal range
6974 foreach ($entities[0] as $entity) {
6975 if ($entity[1]+1-$entities_length <= $left) {
6976 $left--;
6977 $entities_length += strlen($entity[0]);
6978 } else {
6979 // no more characters left
6980 break;
6984 $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
6985 // maximum lenght is reached, so get off the loop
6986 break;
6987 } else {
6988 $truncate .= $line_matchings[2];
6989 $total_length += $content_length;
6992 // if the maximum length is reached, get off the loop
6993 if($total_length >= $ideal) {
6994 break;
6998 // if the words shouldn't be cut in the middle...
6999 if (!$exact) {
7000 // ...search the last occurance of a space...
7001 for ($k=strlen($truncate);$k>0;$k--) {
7002 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
7003 if ($char == '.' or $char == ' ') {
7004 $breakpos = $k+1;
7005 break;
7006 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
7007 $breakpos = $k; // can be truncated at any UTF-8
7008 break; // character boundary.
7013 if (isset($breakpos)) {
7014 // ...and cut the text in this position
7015 $truncate = substr($truncate, 0, $breakpos);
7019 // add the defined ending to the text
7020 $truncate .= $ending;
7022 // Now calculate the list of open html tags based on the truncate position
7023 $open_tags = array();
7024 foreach ($tagdetails as $taginfo) {
7025 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
7026 // Don't include tags after we made the break!
7027 break;
7029 if($taginfo->open) {
7030 // add tag to the beginning of $open_tags list
7031 array_unshift($open_tags, $taginfo->tag);
7032 } else {
7033 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
7034 if ($pos !== false) {
7035 unset($open_tags[$pos]);
7040 // close all unclosed html-tags
7041 foreach ($open_tags as $tag) {
7042 $truncate .= '</' . $tag . '>';
7045 return $truncate;
7050 * Given dates in seconds, how many weeks is the date from startdate
7051 * The first week is 1, the second 2 etc ...
7053 * @uses WEEKSECS
7054 * @param ? $startdate ?
7055 * @param ? $thedate ?
7056 * @return string
7057 * @todo Finish documenting this function
7059 function getweek ($startdate, $thedate) {
7060 if ($thedate < $startdate) { // error
7061 return 0;
7064 return floor(($thedate - $startdate) / WEEKSECS) + 1;
7068 * returns a randomly generated password of length $maxlen. inspired by
7069 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
7070 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
7072 * @param int $maxlen The maximum size of the password being generated.
7073 * @return string
7075 function generate_password($maxlen=10) {
7076 global $CFG;
7078 if (empty($CFG->passwordpolicy)) {
7079 $fillers = PASSWORD_DIGITS;
7080 $wordlist = file($CFG->wordlist);
7081 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7082 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7083 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
7084 $password = $word1 . $filler1 . $word2;
7085 } else {
7086 $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
7087 $digits = $CFG->minpassworddigits;
7088 $lower = $CFG->minpasswordlower;
7089 $upper = $CFG->minpasswordupper;
7090 $nonalphanum = $CFG->minpasswordnonalphanum;
7091 $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
7093 // Make sure we have enough characters to fulfill
7094 // complexity requirements
7095 $passworddigits = PASSWORD_DIGITS;
7096 while ($digits > strlen($passworddigits)) {
7097 $passworddigits .= PASSWORD_DIGITS;
7099 $passwordlower = PASSWORD_LOWER;
7100 while ($lower > strlen($passwordlower)) {
7101 $passwordlower .= PASSWORD_LOWER;
7103 $passwordupper = PASSWORD_UPPER;
7104 while ($upper > strlen($passwordupper)) {
7105 $passwordupper .= PASSWORD_UPPER;
7107 $passwordnonalphanum = PASSWORD_NONALPHANUM;
7108 while ($nonalphanum > strlen($passwordnonalphanum)) {
7109 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
7112 // Now mix and shuffle it all
7113 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
7114 substr(str_shuffle ($passwordupper), 0, $upper) .
7115 substr(str_shuffle ($passworddigits), 0, $digits) .
7116 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
7117 substr(str_shuffle ($passwordlower .
7118 $passwordupper .
7119 $passworddigits .
7120 $passwordnonalphanum), 0 , $additional));
7123 return substr ($password, 0, $maxlen);
7127 * Given a float, prints it nicely.
7128 * Localized floats must not be used in calculations!
7130 * @param float $flaot The float to print
7131 * @param int $places The number of decimal places to print.
7132 * @param bool $localized use localized decimal separator
7133 * @return string locale float
7135 function format_float($float, $decimalpoints=1, $localized=true) {
7136 if (is_null($float)) {
7137 return '';
7139 if ($localized) {
7140 return number_format($float, $decimalpoints, get_string('decsep'), '');
7141 } else {
7142 return number_format($float, $decimalpoints, '.', '');
7147 * Converts locale specific floating point/comma number back to standard PHP float value
7148 * Do NOT try to do any math operations before this conversion on any user submitted floats!
7150 * @param string $locale_float locale aware float representation
7152 function unformat_float($locale_float) {
7153 $locale_float = trim($locale_float);
7155 if ($locale_float == '') {
7156 return null;
7159 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
7161 return (float)str_replace(get_string('decsep'), '.', $locale_float);
7165 * Given a simple array, this shuffles it up just like shuffle()
7166 * Unlike PHP's shuffle() this function works on any machine.
7168 * @param array $array The array to be rearranged
7169 * @return array
7171 function swapshuffle($array) {
7173 srand ((double) microtime() * 10000000);
7174 $last = count($array) - 1;
7175 for ($i=0;$i<=$last;$i++) {
7176 $from = rand(0,$last);
7177 $curr = $array[$i];
7178 $array[$i] = $array[$from];
7179 $array[$from] = $curr;
7181 return $array;
7185 * Like {@link swapshuffle()}, but works on associative arrays
7187 * @param array $array The associative array to be rearranged
7188 * @return array
7190 function swapshuffle_assoc($array) {
7192 $newarray = array();
7193 $newkeys = swapshuffle(array_keys($array));
7195 foreach ($newkeys as $newkey) {
7196 $newarray[$newkey] = $array[$newkey];
7198 return $newarray;
7202 * Given an arbitrary array, and a number of draws,
7203 * this function returns an array with that amount
7204 * of items. The indexes are retained.
7206 * @param array $array ?
7207 * @param ? $draws ?
7208 * @return ?
7209 * @todo Finish documenting this function
7211 function draw_rand_array($array, $draws) {
7212 srand ((double) microtime() * 10000000);
7214 $return = array();
7216 $last = count($array);
7218 if ($draws > $last) {
7219 $draws = $last;
7222 while ($draws > 0) {
7223 $last--;
7225 $keys = array_keys($array);
7226 $rand = rand(0, $last);
7228 $return[$keys[$rand]] = $array[$keys[$rand]];
7229 unset($array[$keys[$rand]]);
7231 $draws--;
7234 return $return;
7238 * microtime_diff
7240 * @param string $a ?
7241 * @param string $b ?
7242 * @return string
7243 * @todo Finish documenting this function
7245 function microtime_diff($a, $b) {
7246 list($a_dec, $a_sec) = explode(' ', $a);
7247 list($b_dec, $b_sec) = explode(' ', $b);
7248 return $b_sec - $a_sec + $b_dec - $a_dec;
7252 * Given a list (eg a,b,c,d,e) this function returns
7253 * an array of 1->a, 2->b, 3->c etc
7255 * @param array $list ?
7256 * @param string $separator ?
7257 * @todo Finish documenting this function
7259 function make_menu_from_list($list, $separator=',') {
7261 $array = array_reverse(explode($separator, $list), true);
7262 foreach ($array as $key => $item) {
7263 $outarray[$key+1] = trim($item);
7265 return $outarray;
7269 * Creates an array that represents all the current grades that
7270 * can be chosen using the given grading type. Negative numbers
7271 * are scales, zero is no grade, and positive numbers are maximum
7272 * grades.
7274 * @param int $gradingtype ?
7275 * return int
7276 * @todo Finish documenting this function
7278 function make_grades_menu($gradingtype) {
7279 $grades = array();
7280 if ($gradingtype < 0) {
7281 if ($scale = get_record('scale', 'id', - $gradingtype)) {
7282 return make_menu_from_list($scale->scale);
7284 } else if ($gradingtype > 0) {
7285 for ($i=$gradingtype; $i>=0; $i--) {
7286 $grades[$i] = $i .' / '. $gradingtype;
7288 return $grades;
7290 return $grades;
7294 * This function returns the nummber of activities
7295 * using scaleid in a courseid
7297 * @param int $courseid ?
7298 * @param int $scaleid ?
7299 * @return int
7300 * @todo Finish documenting this function
7302 function course_scale_used($courseid, $scaleid) {
7304 global $CFG;
7306 $return = 0;
7308 if (!empty($scaleid)) {
7309 if ($cms = get_course_mods($courseid)) {
7310 foreach ($cms as $cm) {
7311 //Check cm->name/lib.php exists
7312 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
7313 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
7314 $function_name = $cm->modname.'_scale_used';
7315 if (function_exists($function_name)) {
7316 if ($function_name($cm->instance,$scaleid)) {
7317 $return++;
7324 // check if any course grade item makes use of the scale
7325 $return += count_records('grade_items', 'courseid', $courseid, 'scaleid', $scaleid);
7327 // check if any outcome in the course makes use of the scale
7328 $return += count_records_sql("SELECT COUNT(*)
7329 FROM {$CFG->prefix}grade_outcomes_courses goc,
7330 {$CFG->prefix}grade_outcomes go
7331 WHERE go.id = goc.outcomeid
7332 AND go.scaleid = $scaleid
7333 AND goc.courseid = $courseid");
7335 return $return;
7339 * This function returns the nummber of activities
7340 * using scaleid in the entire site
7342 * @param int $scaleid ?
7343 * @return int
7344 * @todo Finish documenting this function. Is return type correct?
7346 function site_scale_used($scaleid,&$courses) {
7348 global $CFG;
7350 $return = 0;
7352 if (!is_array($courses) || count($courses) == 0) {
7353 $courses = get_courses("all",false,"c.id,c.shortname");
7356 if (!empty($scaleid)) {
7357 if (is_array($courses) && count($courses) > 0) {
7358 foreach ($courses as $course) {
7359 $return += course_scale_used($course->id,$scaleid);
7363 return $return;
7367 * make_unique_id_code
7369 * @param string $extra ?
7370 * @return string
7371 * @todo Finish documenting this function
7373 function make_unique_id_code($extra='') {
7375 $hostname = 'unknownhost';
7376 if (!empty($_SERVER['HTTP_HOST'])) {
7377 $hostname = $_SERVER['HTTP_HOST'];
7378 } else if (!empty($_ENV['HTTP_HOST'])) {
7379 $hostname = $_ENV['HTTP_HOST'];
7380 } else if (!empty($_SERVER['SERVER_NAME'])) {
7381 $hostname = $_SERVER['SERVER_NAME'];
7382 } else if (!empty($_ENV['SERVER_NAME'])) {
7383 $hostname = $_ENV['SERVER_NAME'];
7386 $date = gmdate("ymdHis");
7388 $random = random_string(6);
7390 if ($extra) {
7391 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
7392 } else {
7393 return $hostname .'+'. $date .'+'. $random;
7399 * Function to check the passed address is within the passed subnet
7401 * The parameter is a comma separated string of subnet definitions.
7402 * Subnet strings can be in one of three formats:
7403 * 1: xxx.xxx.xxx.xxx/xx
7404 * 2: xxx.xxx
7405 * 3: xxx.xxx.xxx.xxx-xxx //a range of IP addresses in the last group.
7406 * Code for type 1 modified from user posted comments by mediator at
7407 * {@link http://au.php.net/manual/en/function.ip2long.php}
7409 * TODO one day we will have to make this work with IP6.
7411 * @param string $addr The address you are checking
7412 * @param string $subnetstr The string of subnet addresses
7413 * @return bool
7415 function address_in_subnet($addr, $subnetstr) {
7417 $subnets = explode(',', $subnetstr);
7418 $found = false;
7419 $addr = trim($addr);
7421 foreach ($subnets as $subnet) {
7422 $subnet = trim($subnet);
7423 if (strpos($subnet, '/') !== false) { /// type 1
7424 list($ip, $mask) = explode('/', $subnet);
7425 if (!is_number($mask) || $mask < 0 || $mask > 32) {
7426 continue;
7428 if ($mask == 0) {
7429 return true;
7431 if ($mask == 32) {
7432 if ($ip === $addr) {
7433 return true;
7435 continue;
7437 $mask = 0xffffffff << (32 - $mask);
7438 $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
7439 } else if (strpos($subnet, '-') !== false) {/// type 3
7440 $subnetparts = explode('.', $subnet);
7441 $addrparts = explode('.', $addr);
7442 $subnetrange = explode('-', array_pop($subnetparts));
7443 if (count($subnetrange) == 2) {
7444 $lastaddrpart = array_pop($addrparts);
7445 $found = ($subnetparts == $addrparts &&
7446 $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
7448 } else { /// type 2
7449 if ($subnet[strlen($subnet) - 1] != '.') {
7450 $subnet .= '.';
7452 $found = (strpos($addr . '.', $subnet) === 0);
7455 if ($found) {
7456 break;
7459 return $found;
7463 * This function sets the $HTTPSPAGEREQUIRED global
7464 * (used in some parts of moodle to change some links)
7465 * and calculate the proper wwwroot to be used
7467 * By using this function properly, we can ensure 100% https-ized pages
7468 * at our entire discretion (login, forgot_password, change_password)
7470 function httpsrequired() {
7472 global $CFG, $HTTPSPAGEREQUIRED;
7474 if (!empty($CFG->loginhttps)) {
7475 $HTTPSPAGEREQUIRED = true;
7476 $CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
7477 $CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
7479 // change theme URLs to https
7480 theme_setup();
7482 } else {
7483 $CFG->httpswwwroot = $CFG->wwwroot;
7484 $CFG->httpsthemewww = $CFG->themewww;
7489 * For outputting debugging info
7491 * @uses STDOUT
7492 * @param string $string ?
7493 * @param string $eol ?
7494 * @todo Finish documenting this function
7496 function mtrace($string, $eol="\n", $sleep=0) {
7498 if (defined('STDOUT')) {
7499 fwrite(STDOUT, $string.$eol);
7500 } else {
7501 echo $string . $eol;
7504 flush();
7506 //delay to keep message on user's screen in case of subsequent redirect
7507 if ($sleep) {
7508 sleep($sleep);
7512 //Replace 1 or more slashes or backslashes to 1 slash
7513 function cleardoubleslashes ($path) {
7514 return preg_replace('/(\/|\\\){1,}/','/',$path);
7517 function zip_files ($originalfiles, $destination) {
7518 //Zip an array of files/dirs to a destination zip file
7519 //Both parameters must be FULL paths to the files/dirs
7521 global $CFG;
7523 //Extract everything from destination
7524 $path_parts = pathinfo(cleardoubleslashes($destination));
7525 $destpath = $path_parts["dirname"]; //The path of the zip file
7526 $destfilename = $path_parts["basename"]; //The name of the zip file
7527 $extension = $path_parts["extension"]; //The extension of the file
7529 //If no file, error
7530 if (empty($destfilename)) {
7531 return false;
7534 //If no extension, add it
7535 if (empty($extension)) {
7536 $extension = 'zip';
7537 $destfilename = $destfilename.'.'.$extension;
7540 //Check destination path exists
7541 if (!is_dir($destpath)) {
7542 return false;
7545 //Check destination path is writable. TODO!!
7547 //Clean destination filename
7548 $destfilename = clean_filename($destfilename);
7550 //Now check and prepare every file
7551 $files = array();
7552 $origpath = NULL;
7554 foreach ($originalfiles as $file) { //Iterate over each file
7555 //Check for every file
7556 $tempfile = cleardoubleslashes($file); // no doubleslashes!
7557 //Calculate the base path for all files if it isn't set
7558 if ($origpath === NULL) {
7559 $origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
7561 //See if the file is readable
7562 if (!is_readable($tempfile)) { //Is readable
7563 continue;
7565 //See if the file/dir is in the same directory than the rest
7566 if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
7567 continue;
7569 //Add the file to the array
7570 $files[] = $tempfile;
7573 //Everything is ready:
7574 // -$origpath is the path where ALL the files to be compressed reside (dir).
7575 // -$destpath is the destination path where the zip file will go (dir).
7576 // -$files is an array of files/dirs to compress (fullpath)
7577 // -$destfilename is the name of the zip file (without path)
7579 //print_object($files); //Debug
7581 if (empty($CFG->zip)) { // Use built-in php-based zip function
7583 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7584 //rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
7585 $zipfiles = array();
7586 $start = strlen($origpath)+1;
7587 foreach($files as $file) {
7588 $tf = array();
7589 $tf[PCLZIP_ATT_FILE_NAME] = $file;
7590 $tf[PCLZIP_ATT_FILE_NEW_FULL_NAME] = substr($file, $start);
7591 $zipfiles[] = $tf;
7593 //create the archive
7594 $archive = new PclZip(cleardoubleslashes("$destpath/$destfilename"));
7595 if (($list = $archive->create($zipfiles) == 0)) {
7596 notice($archive->errorInfo(true));
7597 return false;
7600 } else { // Use external zip program
7602 $filestozip = "";
7603 foreach ($files as $filetozip) {
7604 $filestozip .= escapeshellarg(basename($filetozip));
7605 $filestozip .= " ";
7607 //Construct the command
7608 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7609 $command = 'cd '.escapeshellarg($origpath).$separator.
7610 escapeshellarg($CFG->zip).' -r '.
7611 escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
7612 //All converted to backslashes in WIN
7613 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7614 $command = str_replace('/','\\',$command);
7616 Exec($command);
7618 return true;
7621 function unzip_file ($zipfile, $destination = '', $showstatus = true) {
7622 //Unzip one zip file to a destination dir
7623 //Both parameters must be FULL paths
7624 //If destination isn't specified, it will be the
7625 //SAME directory where the zip file resides.
7627 global $CFG;
7629 //Extract everything from zipfile
7630 $path_parts = pathinfo(cleardoubleslashes($zipfile));
7631 $zippath = $path_parts["dirname"]; //The path of the zip file
7632 $zipfilename = $path_parts["basename"]; //The name of the zip file
7633 $extension = $path_parts["extension"]; //The extension of the file
7635 //If no file, error
7636 if (empty($zipfilename)) {
7637 return false;
7640 //If no extension, error
7641 if (empty($extension)) {
7642 return false;
7645 //Clear $zipfile
7646 $zipfile = cleardoubleslashes($zipfile);
7648 //Check zipfile exists
7649 if (!file_exists($zipfile)) {
7650 return false;
7653 //If no destination, passed let's go with the same directory
7654 if (empty($destination)) {
7655 $destination = $zippath;
7658 //Clear $destination
7659 $destpath = rtrim(cleardoubleslashes($destination), "/");
7661 //Check destination path exists
7662 if (!is_dir($destpath)) {
7663 return false;
7666 //Check destination path is writable. TODO!!
7668 //Everything is ready:
7669 // -$zippath is the path where the zip file resides (dir)
7670 // -$zipfilename is the name of the zip file (without path)
7671 // -$destpath is the destination path where the zip file will uncompressed (dir)
7673 $list = array();
7675 require_once("$CFG->libdir/filelib.php");
7677 do {
7678 $temppath = "$CFG->dataroot/temp/unzip/".random_string(10);
7679 } while (file_exists($temppath));
7680 if (!check_dir_exists($temppath, true, true)) {
7681 return false;
7684 if (empty($CFG->unzip)) { // Use built-in php-based unzip function
7686 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7687 $archive = new PclZip(cleardoubleslashes("$zippath/$zipfilename"));
7688 if (!$list = $archive->extract(PCLZIP_OPT_PATH, $temppath,
7689 PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename',
7690 PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $temppath)) {
7691 if (!empty($showstatus)) {
7692 notice($archive->errorInfo(true));
7694 return false;
7697 } else { // Use external unzip program
7699 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7700 $redirection = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? '' : ' 2>&1';
7702 $command = 'cd '.escapeshellarg($zippath).$separator.
7703 escapeshellarg($CFG->unzip).' -o '.
7704 escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
7705 escapeshellarg($temppath).$redirection;
7706 //All converted to backslashes in WIN
7707 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7708 $command = str_replace('/','\\',$command);
7710 Exec($command,$list);
7713 unzip_process_temp_dir($temppath, $destpath);
7714 fulldelete($temppath);
7716 //Display some info about the unzip execution
7717 if ($showstatus) {
7718 unzip_show_status($list, $temppath, $destpath);
7721 return true;
7725 * Sanitize temporary unzipped files and move to target dir.
7726 * @param string $temppath path to temporary dir with unzip output
7727 * @param string $destpath destination path
7728 * @return void
7730 function unzip_process_temp_dir($temppath, $destpath) {
7731 global $CFG;
7733 $filepermissions = ($CFG->directorypermissions & 0666); // strip execute flags
7735 if (check_dir_exists($destpath, true, true)) {
7736 $currdir = opendir($temppath);
7737 while (false !== ($file = readdir($currdir))) {
7738 if ($file <> ".." && $file <> ".") {
7739 $fullfile = "$temppath/$file";
7740 if (is_link($fullfile)) {
7741 //somebody tries to sneak in symbolik link - no way!
7742 continue;
7744 $cleanfile = clean_param($file, PARAM_FILE); // no dangerous chars
7745 if ($cleanfile === '') {
7746 // invalid file name
7747 continue;
7749 if ($cleanfile !== $file and file_exists("$temppath/$cleanfile")) {
7750 // eh, weird chars collision detected
7751 continue;
7753 $descfile = "$destpath/$cleanfile";
7754 if (is_dir($fullfile)) {
7755 // recurse into subdirs
7756 unzip_process_temp_dir($fullfile, $descfile);
7758 if (is_file($fullfile)) {
7759 // rename and move the file
7760 if (file_exists($descfile)) {
7761 //override existing files
7762 unlink($descfile);
7764 rename($fullfile, $descfile);
7765 chmod($descfile, $filepermissions);
7769 closedir($currdir);
7773 function unzip_cleanfilename ($p_event, &$p_header) {
7774 //This function is used as callback in unzip_file() function
7775 //to clean illegal characters for given platform and to prevent directory traversal.
7776 //Produces the same result as info-zip unzip.
7777 $p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
7778 $p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
7779 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7780 $p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
7781 $p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
7782 } else {
7783 //Add filtering for other systems here
7784 // BSD: none (tested)
7785 // Linux: ??
7786 // MacosX: ??
7788 $p_header['filename'] = cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
7789 return 1;
7792 function unzip_show_status($list, $removepath, $removepath2) {
7793 //This function shows the results of the unzip execution
7794 //depending of the value of the $CFG->zip, results will be
7795 //text or an array of files.
7797 global $CFG;
7799 if (empty($CFG->unzip)) { // Use built-in php-based zip function
7800 $strname = get_string("name");
7801 $strsize = get_string("size");
7802 $strmodified = get_string("modified");
7803 $strstatus = get_string("status");
7804 echo "<table width=\"640\">";
7805 echo "<tr><th class=\"header\" scope=\"col\">$strname</th>";
7806 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strsize</th>";
7807 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strmodified</th>";
7808 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strstatus</th></tr>";
7809 foreach ($list as $item) {
7810 echo "<tr>";
7811 $item['filename'] = str_replace(cleardoubleslashes($removepath).'/', "", $item['filename']);
7812 $item['filename'] = str_replace(cleardoubleslashes($removepath2).'/', "", $item['filename']);
7813 echo '<td align="left" style="white-space:nowrap ">'.s(clean_param($item['filename'], PARAM_PATH)).'</td>';
7814 if (! $item['folder']) {
7815 echo '<td align="right" style="white-space:nowrap ">'.display_size($item['size']).'</td>';
7816 } else {
7817 echo "<td>&nbsp;</td>";
7819 $filedate = userdate($item['mtime'], get_string("strftimedatetime"));
7820 echo '<td align="right" style="white-space:nowrap ">'.$filedate.'</td>';
7821 echo '<td align="right" style="white-space:nowrap ">'.$item['status'].'</td>';
7822 echo "</tr>";
7824 echo "</table>";
7826 } else { // Use external zip program
7827 print_simple_box_start("center");
7828 echo "<pre>";
7829 foreach ($list as $item) {
7830 $item = str_replace(cleardoubleslashes($removepath.'/'), '', $item);
7831 $item = str_replace(cleardoubleslashes($removepath2.'/'), '', $item);
7832 echo s($item).'<br />';
7834 echo "</pre>";
7835 print_simple_box_end();
7840 * Returns most reliable client address
7842 * @return string The remote IP address
7844 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
7845 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
7846 function getremoteaddr() {
7847 global $CFG;
7849 if (empty($CFG->getremoteaddrconf)) {
7850 // This will happen, for example, before just after the upgrade, as the
7851 // user is redirected to the admin screen.
7852 $variablestoskip = 0;
7853 } else {
7854 $variablestoskip = $CFG->getremoteaddrconf;
7856 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
7857 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
7858 return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
7861 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
7862 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
7863 return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
7866 if (!empty($_SERVER['REMOTE_ADDR'])) {
7867 return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
7868 } else {
7869 return null;
7874 * Cleans a remote address ready to put into the log table
7876 function cleanremoteaddr($addr) {
7877 $originaladdr = $addr;
7878 $matches = array();
7879 // first get all things that look like IP addresses.
7880 if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER)) {
7881 return '';
7883 $goodmatches = array();
7884 $lanmatches = array();
7885 foreach ($matches as $match) {
7886 // print_r($match);
7887 // check to make sure it's not an internal address.
7888 // the following are reserved for private lans...
7889 // 10.0.0.0 - 10.255.255.255
7890 // 172.16.0.0 - 172.31.255.255
7891 // 192.168.0.0 - 192.168.255.255
7892 // 169.254.0.0 -169.254.255.255
7893 $bits = explode('.',$match[0]);
7894 if (count($bits) != 4) {
7895 // weird, preg match shouldn't give us it.
7896 continue;
7898 if (($bits[0] == 10)
7899 || ($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
7900 || ($bits[0] == 192 && $bits[1] == 168)
7901 || ($bits[0] == 169 && $bits[1] == 254)) {
7902 $lanmatches[] = $match[0];
7903 continue;
7905 // finally, it's ok
7906 $goodmatches[] = $match[0];
7908 if (!count($goodmatches)) {
7909 // perhaps we have a lan match, it's probably better to return that.
7910 if (!count($lanmatches)) {
7911 return '';
7912 } else {
7913 return array_pop($lanmatches);
7916 if (count($goodmatches) == 1) {
7917 return $goodmatches[0];
7919 //Commented out following because there are so many, and it clogs the logs MDL-13544
7920 //error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
7922 // We need to return something, so return the first
7923 return array_pop($goodmatches);
7927 * file_put_contents is only supported by php 5.0 and higher
7928 * so if it is not predefined, define it here
7930 * @param $file full path of the file to write
7931 * @param $contents contents to be sent
7932 * @return number of bytes written (false on error)
7934 if(!function_exists('file_put_contents')) {
7935 function file_put_contents($file, $contents) {
7936 $result = false;
7937 if ($f = fopen($file, 'w')) {
7938 $result = fwrite($f, $contents);
7939 fclose($f);
7941 return $result;
7946 * The clone keyword is only supported from PHP 5 onwards.
7947 * The behaviour of $obj2 = $obj1 differs fundamentally
7948 * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
7949 * created, in PHP 5 $obj1 is referenced. To create a copy
7950 * in PHP 5 the clone keyword was introduced. This function
7951 * simulates this behaviour for PHP < 5.0.0.
7952 * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
7954 * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
7955 * Found a better implementation (more checks and possibilities) from PEAR:
7956 * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
7958 * @param object $obj
7959 * @return object
7961 if(!check_php_version('5.0.0')) {
7962 // the eval is needed to prevent PHP 5 from getting a parse error!
7963 eval('
7964 function clone($obj) {
7965 /// Sanity check
7966 if (!is_object($obj)) {
7967 user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
7968 return;
7971 /// Use serialize/unserialize trick to deep copy the object
7972 $obj = unserialize(serialize($obj));
7974 /// If there is a __clone method call it on the "new" class
7975 if (method_exists($obj, \'__clone\')) {
7976 $obj->__clone();
7979 return $obj;
7982 // Supply the PHP5 function scandir() to older versions.
7983 function scandir($directory) {
7984 $files = array();
7985 if ($dh = opendir($directory)) {
7986 while (($file = readdir($dh)) !== false) {
7987 $files[] = $file;
7989 closedir($dh);
7991 return $files;
7994 // Supply the PHP5 function array_combine() to older versions.
7995 function array_combine($keys, $values) {
7996 if (!is_array($keys) || !is_array($values) || count($keys) != count($values)) {
7997 return false;
7999 reset($values);
8000 $result = array();
8001 foreach ($keys as $key) {
8002 $result[$key] = current($values);
8003 next($values);
8005 return $result;
8011 * This function will make a complete copy of anything it's given,
8012 * regardless of whether it's an object or not.
8013 * @param mixed $thing
8014 * @return mixed
8016 function fullclone($thing) {
8017 return unserialize(serialize($thing));
8022 * This function expects to called during shutdown
8023 * should be set via register_shutdown_function()
8024 * in lib/setup.php .
8026 * Right now we do it only if we are under apache, to
8027 * make sure apache children that hog too much mem are
8028 * killed.
8031 function moodle_request_shutdown() {
8033 global $CFG;
8035 // initially, we are only ever called under apache
8036 // but check just in case
8037 if (function_exists('apache_child_terminate')
8038 && function_exists('memory_get_usage')
8039 && ini_get_bool('child_terminate')) {
8040 if (empty($CFG->apachemaxmem)) {
8041 $CFG->apachemaxmem = 25000000; // default 25MiB
8043 if (memory_get_usage() > (int)$CFG->apachemaxmem) {
8044 trigger_error('Mem usage over $CFG->apachemaxmem: marking child for reaping.');
8045 @apache_child_terminate();
8048 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
8049 if (defined('MDL_PERFTOLOG')) {
8050 $perf = get_performance_info();
8051 error_log("PERF: " . $perf['txt']);
8053 if (defined('MDL_PERFINC')) {
8054 $inc = get_included_files();
8055 $ts = 0;
8056 foreach($inc as $f) {
8057 if (preg_match(':^/:', $f)) {
8058 $fs = filesize($f);
8059 $ts += $fs;
8060 $hfs = display_size($fs);
8061 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
8062 , NULL, NULL, 0);
8063 } else {
8064 error_log($f , NULL, NULL, 0);
8067 if ($ts > 0 ) {
8068 $hts = display_size($ts);
8069 error_log("Total size of files included: $ts ($hts)");
8076 * If new messages are waiting for the current user, then return
8077 * Javascript code to create a popup window
8079 * @return string Javascript code
8081 function message_popup_window() {
8082 global $USER;
8084 $popuplimit = 30; // Minimum seconds between popups
8086 if (!defined('MESSAGE_WINDOW')) {
8087 if (!empty($USER->id) and !isguestuser()) {
8088 if (!isset($USER->message_lastpopup)) {
8089 $USER->message_lastpopup = 0;
8091 if ((time() - $USER->message_lastpopup) > $popuplimit) { /// It's been long enough
8092 if (get_user_preferences('message_showmessagewindow', 1) == 1) {
8093 if (count_records_select('message', 'useridto = \''.$USER->id.'\' AND timecreated > \''.$USER->message_lastpopup.'\'')) {
8094 $USER->message_lastpopup = time();
8095 return '<script type="text/javascript">'."\n//<![CDATA[\n openpopup('/message/index.php', 'message',
8096 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0);\n//]]>\n</script>";
8103 return '';
8106 // Used to make sure that $min <= $value <= $max
8107 function bounded_number($min, $value, $max) {
8108 if($value < $min) {
8109 return $min;
8111 if($value > $max) {
8112 return $max;
8114 return $value;
8117 function array_is_nested($array) {
8118 foreach ($array as $value) {
8119 if (is_array($value)) {
8120 return true;
8123 return false;
8127 *** get_performance_info() pairs up with init_performance_info()
8128 *** loaded in setup.php. Returns an array with 'html' and 'txt'
8129 *** values ready for use, and each of the individual stats provided
8130 *** separately as well.
8133 function get_performance_info() {
8134 global $CFG, $PERF, $rcache;
8136 $info = array();
8137 $info['html'] = ''; // holds userfriendly HTML representation
8138 $info['txt'] = me() . ' '; // holds log-friendly representation
8140 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
8142 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
8143 $info['txt'] .= 'time: '.$info['realtime'].'s ';
8145 if (function_exists('memory_get_usage')) {
8146 $info['memory_total'] = memory_get_usage();
8147 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
8148 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
8149 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
8152 if (function_exists('memory_get_peak_usage')) {
8153 $info['memory_peak'] = memory_get_peak_usage();
8154 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
8155 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
8158 $inc = get_included_files();
8159 //error_log(print_r($inc,1));
8160 $info['includecount'] = count($inc);
8161 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
8162 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
8164 if (!empty($PERF->dbqueries)) {
8165 $info['dbqueries'] = $PERF->dbqueries;
8166 $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
8167 $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
8170 if (!empty($PERF->logwrites)) {
8171 $info['logwrites'] = $PERF->logwrites;
8172 $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
8173 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
8176 if (!empty($PERF->profiling) && $PERF->profiling) {
8177 require_once($CFG->dirroot .'/lib/profilerlib.php');
8178 $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
8181 if (function_exists('posix_times')) {
8182 $ptimes = posix_times();
8183 if (is_array($ptimes)) {
8184 foreach ($ptimes as $key => $val) {
8185 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
8187 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
8188 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
8192 // Grab the load average for the last minute
8193 // /proc will only work under some linux configurations
8194 // while uptime is there under MacOSX/Darwin and other unices
8195 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
8196 list($server_load) = explode(' ', $loadavg[0]);
8197 unset($loadavg);
8198 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
8199 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
8200 $server_load = $matches[1];
8201 } else {
8202 trigger_error('Could not parse uptime output!');
8205 if (!empty($server_load)) {
8206 $info['serverload'] = $server_load;
8207 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
8208 $info['txt'] .= "serverload: {$info['serverload']} ";
8211 if (isset($rcache->hits) && isset($rcache->misses)) {
8212 $info['rcachehits'] = $rcache->hits;
8213 $info['rcachemisses'] = $rcache->misses;
8214 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
8215 "{$rcache->hits}/{$rcache->misses}</span> ";
8216 $info['txt'] .= 'rcache: '.
8217 "{$rcache->hits}/{$rcache->misses} ";
8219 $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
8220 return $info;
8223 function apd_get_profiling() {
8224 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
8228 * Delete directory or only it's content
8229 * @param string $dir directory path
8230 * @param bool $content_only
8231 * @return bool success, true also if dir does not exist
8233 function remove_dir($dir, $content_only=false) {
8234 if (!file_exists($dir)) {
8235 // nothing to do
8236 return true;
8238 $handle = opendir($dir);
8239 $result = true;
8240 while (false!==($item = readdir($handle))) {
8241 if($item != '.' && $item != '..') {
8242 if(is_dir($dir.'/'.$item)) {
8243 $result = remove_dir($dir.'/'.$item) && $result;
8244 }else{
8245 $result = unlink($dir.'/'.$item) && $result;
8249 closedir($handle);
8250 if ($content_only) {
8251 return $result;
8253 return rmdir($dir); // if anything left the result will be false, noo need for && $result
8257 * Function to check if a directory exists and optionally create it.
8259 * @param string absolute directory path (must be under $CFG->dataroot)
8260 * @param boolean create directory if does not exist
8261 * @param boolean create directory recursively
8263 * @return boolean true if directory exists or created
8265 function check_dir_exists($dir, $create=false, $recursive=false) {
8267 global $CFG;
8269 if (strstr(cleardoubleslashes($dir), cleardoubleslashes($CFG->dataroot.'/')) === false) {
8270 debugging('Warning. Wrong call to check_dir_exists(). $dir must be an absolute path under $CFG->dataroot ("' . $dir . '" is incorrect)', DEBUG_DEVELOPER);
8273 $status = true;
8275 if(!is_dir($dir)) {
8276 if (!$create) {
8277 $status = false;
8278 } else {
8279 umask(0000);
8280 if ($recursive) {
8281 /// We are going to make it recursive under $CFG->dataroot only
8282 /// (will help sites running open_basedir security and others)
8283 $dir = str_replace(cleardoubleslashes($CFG->dataroot . '/'), '', cleardoubleslashes($dir));
8284 /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-(
8285 $dirs = explode('/', $dir); /// Extract path parts
8286 /// Iterate over each part with start point $CFG->dataroot
8287 $dir = $CFG->dataroot . '/';
8288 foreach ($dirs as $part) {
8289 if ($part == '') {
8290 continue;
8292 $dir .= $part.'/';
8293 if (!is_dir($dir)) {
8294 if (!mkdir($dir, $CFG->directorypermissions)) {
8295 $status = false;
8296 break;
8300 } else {
8301 $status = mkdir($dir, $CFG->directorypermissions);
8305 return $status;
8308 function report_session_error() {
8309 global $CFG, $FULLME;
8311 if (empty($CFG->lang)) {
8312 $CFG->lang = "en";
8314 // Set up default theme and locale
8315 theme_setup();
8316 moodle_setlocale();
8318 //clear session cookies
8319 if (check_php_version('5.2.0')) {
8320 //PHP 5.2.0
8321 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8322 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8323 } else {
8324 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8325 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8327 //increment database error counters
8328 if (isset($CFG->session_error_counter)) {
8329 set_config('session_error_counter', 1 + $CFG->session_error_counter);
8330 } else {
8331 set_config('session_error_counter', 1);
8333 redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
8338 * Detect if an object or a class contains a given property
8339 * will take an actual object or the name of a class
8340 * @param mix $obj Name of class or real object to test
8341 * @param string $property name of property to find
8342 * @return bool true if property exists
8344 function object_property_exists( $obj, $property ) {
8345 if (is_string( $obj )) {
8346 $properties = get_class_vars( $obj );
8348 else {
8349 $properties = get_object_vars( $obj );
8351 return array_key_exists( $property, $properties );
8356 * Detect a custom script replacement in the data directory that will
8357 * replace an existing moodle script
8358 * @param string $urlpath path to the original script
8359 * @return string full path name if a custom script exists
8360 * @return bool false if no custom script exists
8362 function custom_script_path($urlpath='') {
8363 global $CFG;
8365 // set default $urlpath, if necessary
8366 if (empty($urlpath)) {
8367 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
8370 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
8371 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
8372 return false;
8375 // replace wwwroot with the path to the customscripts folder and clean path
8376 $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
8378 // remove the query string, if any
8379 if (($strpos = strpos($scriptpath, '?')) !== false) {
8380 $scriptpath = substr($scriptpath, 0, $strpos);
8383 // remove trailing slashes, if any
8384 $scriptpath = rtrim($scriptpath, '/\\');
8386 // append index.php, if necessary
8387 if (is_dir($scriptpath)) {
8388 $scriptpath .= '/index.php';
8391 // check the custom script exists
8392 if (file_exists($scriptpath)) {
8393 return $scriptpath;
8394 } else {
8395 return false;
8400 * Wrapper function to load necessary editor scripts
8401 * to $CFG->editorsrc array. Params can be coursei id
8402 * or associative array('courseid' => value, 'name' => 'editorname').
8403 * @uses $CFG
8404 * @param mixed $args Courseid or associative array.
8406 function loadeditor($args) {
8407 global $CFG;
8408 include($CFG->libdir .'/editorlib.php');
8409 return editorObject::loadeditor($args);
8413 * Returns whether or not the user object is a remote MNET user. This function
8414 * is in moodlelib because it does not rely on loading any of the MNET code.
8416 * @param object $user A valid user object
8417 * @return bool True if the user is from a remote Moodle.
8419 function is_mnet_remote_user($user) {
8420 global $CFG;
8422 if (!isset($CFG->mnet_localhost_id)) {
8423 include_once $CFG->dirroot . '/mnet/lib.php';
8424 $env = new mnet_environment();
8425 $env->init();
8426 unset($env);
8429 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
8433 * Checks if a given plugin is in the list of enabled enrolment plugins.
8435 * @param string $auth Enrolment plugin.
8436 * @return boolean Whether the plugin is enabled.
8438 function is_enabled_enrol($enrol='') {
8439 global $CFG;
8441 // use the global default if not specified
8442 if ($enrol == '') {
8443 $enrol = $CFG->enrol;
8445 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
8449 * This function will search for browser prefereed languages, setting Moodle
8450 * to use the best one available if $SESSION->lang is undefined
8452 function setup_lang_from_browser() {
8454 global $CFG, $SESSION, $USER;
8456 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
8457 // Lang is defined in session or user profile, nothing to do
8458 return;
8461 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
8462 return;
8465 /// Extract and clean langs from headers
8466 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
8467 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
8468 $rawlangs = explode(',', $rawlangs); // Convert to array
8469 $langs = array();
8471 $order = 1.0;
8472 foreach ($rawlangs as $lang) {
8473 if (strpos($lang, ';') === false) {
8474 $langs[(string)$order] = $lang;
8475 $order = $order-0.01;
8476 } else {
8477 $parts = explode(';', $lang);
8478 $pos = strpos($parts[1], '=');
8479 $langs[substr($parts[1], $pos+1)] = $parts[0];
8482 krsort($langs, SORT_NUMERIC);
8484 $langlist = get_list_of_languages();
8486 /// Look for such langs under standard locations
8487 foreach ($langs as $lang) {
8488 $lang = strtolower(clean_param($lang.'_utf8', PARAM_SAFEDIR)); // clean it properly for include
8489 if (!array_key_exists($lang, $langlist)) {
8490 continue; // language not allowed, try next one
8492 if (file_exists($CFG->dataroot .'/lang/'. $lang) or file_exists($CFG->dirroot .'/lang/'. $lang)) {
8493 $SESSION->lang = $lang; /// Lang exists, set it in session
8494 break; /// We have finished. Go out
8497 return;
8501 ////////////////////////////////////////////////////////////////////////////////
8503 function is_newnav($navigation) {
8504 if (is_array($navigation) && !empty($navigation['newnav'])) {
8505 return true;
8506 } else {
8507 return false;
8512 * Checks whether the given variable name is defined as a variable within the given object.
8513 * @note This will NOT work with stdClass objects, which have no class variables.
8514 * @param string $var The variable name
8515 * @param object $object The object to check
8516 * @return boolean
8518 function in_object_vars($var, $object) {
8519 $class_vars = get_class_vars(get_class($object));
8520 $class_vars = array_keys($class_vars);
8521 return in_array($var, $class_vars);
8525 * Returns an array without repeated objects.
8526 * This function is similar to array_unique, but for arrays that have objects as values
8528 * @param unknown_type $array
8529 * @param unknown_type $keep_key_assoc
8530 * @return unknown
8532 function object_array_unique($array, $keep_key_assoc = true) {
8533 $duplicate_keys = array();
8534 $tmp = array();
8536 foreach ($array as $key=>$val) {
8537 // convert objects to arrays, in_array() does not support objects
8538 if (is_object($val)) {
8539 $val = (array)$val;
8542 if (!in_array($val, $tmp)) {
8543 $tmp[] = $val;
8544 } else {
8545 $duplicate_keys[] = $key;
8549 foreach ($duplicate_keys as $key) {
8550 unset($array[$key]);
8553 return $keep_key_assoc ? $array : array_values($array);
8557 * Returns the language string for the given plugin.
8559 * @param string $plugin the plugin code name
8560 * @param string $type the type of plugin (mod, block, filter)
8561 * @return string The plugin language string
8563 function get_plugin_name($plugin, $type='mod') {
8564 $plugin_name = '';
8566 switch ($type) {
8567 case 'mod':
8568 $plugin_name = get_string('modulename', $plugin);
8569 break;
8570 case 'blocks':
8571 $plugin_name = get_string('blockname', "block_$plugin");
8572 if (empty($plugin_name) || $plugin_name == '[[blockname]]') {
8573 if (($block = block_instance($plugin)) !== false) {
8574 $plugin_name = $block->get_title();
8575 } else {
8576 $plugin_name = "[[$plugin]]";
8579 break;
8580 case 'filter':
8581 $plugin_name = trim(get_string('filtername', $plugin));
8582 if (empty($plugin_name) or ($plugin_name == '[[filtername]]')) {
8583 $textlib = textlib_get_instance();
8584 $plugin_name = $textlib->strtotitle($plugin);
8586 break;
8587 default:
8588 $plugin_name = $plugin;
8589 break;
8592 return $plugin_name;
8596 * Is a userid the primary administrator?
8598 * @param $userid int id of user to check
8599 * @return boolean
8601 function is_primary_admin($userid){
8602 $primaryadmin = get_admin();
8604 if($userid == $primaryadmin->id){
8605 return true;
8606 }else{
8607 return false;
8612 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
8614 function get_site_identifier() {
8615 global $CFG;
8616 // Check to see if it is missing. If so, initialise it.
8617 if (empty($CFG->siteidentifier)) {
8618 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
8620 // Return it.
8621 return $CFG->siteidentifier;
8624 // vim:autoindent:expandtab:shiftwidth=4:tabstop=4:tw=140: