Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / lib / moodlelib.php
blobbb0e19d933c9ce51d41cc2346a22ca26ac6b437f
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 * @version $Id$
35 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
36 * @package moodlecore
39 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
41 /**
42 * Used by some scripts to check they are being called by Moodle
44 define('MOODLE_INTERNAL', true);
46 /// Date and time constants ///
47 /**
48 * Time constant - the number of seconds in a year
51 define('YEARSECS', 31536000);
53 /**
54 * Time constant - the number of seconds in a week
56 define('WEEKSECS', 604800);
58 /**
59 * Time constant - the number of seconds in a day
61 define('DAYSECS', 86400);
63 /**
64 * Time constant - the number of seconds in an hour
66 define('HOURSECS', 3600);
68 /**
69 * Time constant - the number of seconds in a minute
71 define('MINSECS', 60);
73 /**
74 * Time constant - the number of minutes in a day
76 define('DAYMINS', 1440);
78 /**
79 * Time constant - the number of minutes in an hour
81 define('HOURMINS', 60);
83 /// Parameter constants - every call to optional_param(), required_param() ///
84 /// or clean_param() should have a specified type of parameter. //////////////
86 /**
87 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
88 * originally was 0, but changed because we need to detect unknown
89 * parameter types and swiched order in clean_param().
91 define('PARAM_RAW', 666);
93 /**
94 * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
95 * It was one of the first types, that is why it is abused so much ;-)
97 define('PARAM_CLEAN', 0x0001);
99 /**
100 * PARAM_INT - integers only, use when expecting only numbers.
102 define('PARAM_INT', 0x0002);
105 * PARAM_INTEGER - an alias for PARAM_INT
107 define('PARAM_INTEGER', 0x0002);
110 * PARAM_NUMBER - a real/floating point number.
112 define('PARAM_NUMBER', 0x000a);
115 * PARAM_ALPHA - contains only english letters.
117 define('PARAM_ALPHA', 0x0004);
120 * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
121 * @TODO: should we alias it to PARAM_ALPHANUM ?
123 define('PARAM_ACTION', 0x0004);
126 * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
127 * @TODO: should we alias it to PARAM_ALPHANUM ?
129 define('PARAM_FORMAT', 0x0004);
132 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
134 define('PARAM_NOTAGS', 0x0008);
137 * PARAM_MULTILANG - alias of PARAM_TEXT.
139 define('PARAM_MULTILANG', 0x0009);
142 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
144 define('PARAM_TEXT', 0x0009);
147 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
149 define('PARAM_FILE', 0x0010);
152 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
154 define('PARAM_TAG', 0x0011);
157 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
159 define('PARAM_TAGLIST', 0x0012);
162 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
163 * note: the leading slash is not removed, window drive letter is not allowed
165 define('PARAM_PATH', 0x0020);
168 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
170 define('PARAM_HOST', 0x0040);
173 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
175 define('PARAM_URL', 0x0080);
178 * 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!)
180 define('PARAM_LOCALURL', 0x0180);
183 * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
184 * use when you want to store a new file submitted by students
186 define('PARAM_CLEANFILE',0x0200);
189 * PARAM_ALPHANUM - expected numbers and letters only.
191 define('PARAM_ALPHANUM', 0x0400);
194 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
196 define('PARAM_BOOL', 0x0800);
199 * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
200 * note: do not forget to addslashes() before storing into database!
202 define('PARAM_CLEANHTML',0x1000);
205 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
206 * suitable for include() and require()
207 * @TODO: should we rename this function to PARAM_SAFEDIRS??
209 define('PARAM_ALPHAEXT', 0x2000);
212 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
214 define('PARAM_SAFEDIR', 0x4000);
217 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
219 define('PARAM_SEQUENCE', 0x8000);
222 * PARAM_PEM - Privacy Enhanced Mail format
224 define('PARAM_PEM', 0x10000);
227 * PARAM_BASE64 - Base 64 encoded format
229 define('PARAM_BASE64', 0x20000);
232 /// Page types ///
234 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
236 define('PAGE_COURSE_VIEW', 'course-view');
238 /// Debug levels ///
239 /** no warnings at all */
240 define ('DEBUG_NONE', 0);
241 /** E_ERROR | E_PARSE */
242 define ('DEBUG_MINIMAL', 5);
243 /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
244 define ('DEBUG_NORMAL', 15);
245 /** E_ALL without E_STRICT for now, do show recoverable fatal errors */
246 define ('DEBUG_ALL', 6143);
247 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
248 define ('DEBUG_DEVELOPER', 38911);
251 * Blog access level constant declaration
253 define ('BLOG_USER_LEVEL', 1);
254 define ('BLOG_GROUP_LEVEL', 2);
255 define ('BLOG_COURSE_LEVEL', 3);
256 define ('BLOG_SITE_LEVEL', 4);
257 define ('BLOG_GLOBAL_LEVEL', 5);
260 * Tag constanst
262 //To prevent problems with multibytes strings, this should not exceed the
263 //length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
264 define('TAG_MAX_LENGTH', 50);
267 * Password policy constants
269 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
270 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
271 define ('PASSWORD_DIGITS', '0123456789');
272 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
274 if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
275 define('SORT_LOCALE_STRING', SORT_STRING);
279 /// PARAMETER HANDLING ////////////////////////////////////////////////////
282 * Returns a particular value for the named variable, taken from
283 * POST or GET. If the parameter doesn't exist then an error is
284 * thrown because we require this variable.
286 * This function should be used to initialise all required values
287 * in a script that are based on parameters. Usually it will be
288 * used like this:
289 * $id = required_param('id');
291 * @param string $parname the name of the page parameter we want
292 * @param int $type expected type of parameter
293 * @return mixed
295 function required_param($parname, $type=PARAM_CLEAN) {
297 // detect_unchecked_vars addition
298 global $CFG;
299 if (!empty($CFG->detect_unchecked_vars)) {
300 global $UNCHECKED_VARS;
301 unset ($UNCHECKED_VARS->vars[$parname]);
304 if (isset($_POST[$parname])) { // POST has precedence
305 $param = $_POST[$parname];
306 } else if (isset($_GET[$parname])) {
307 $param = $_GET[$parname];
308 } else {
309 error('A required parameter ('.$parname.') was missing');
312 return clean_param($param, $type);
316 * Returns a particular value for the named variable, taken from
317 * POST or GET, otherwise returning a given default.
319 * This function should be used to initialise all optional values
320 * in a script that are based on parameters. Usually it will be
321 * used like this:
322 * $name = optional_param('name', 'Fred');
324 * @param string $parname the name of the page parameter we want
325 * @param mixed $default the default value to return if nothing is found
326 * @param int $type expected type of parameter
327 * @return mixed
329 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
331 // detect_unchecked_vars addition
332 global $CFG;
333 if (!empty($CFG->detect_unchecked_vars)) {
334 global $UNCHECKED_VARS;
335 unset ($UNCHECKED_VARS->vars[$parname]);
338 if (isset($_POST[$parname])) { // POST has precedence
339 $param = $_POST[$parname];
340 } else if (isset($_GET[$parname])) {
341 $param = $_GET[$parname];
342 } else {
343 return $default;
346 return clean_param($param, $type);
350 * Used by {@link optional_param()} and {@link required_param()} to
351 * clean the variables and/or cast to specific types, based on
352 * an options field.
353 * <code>
354 * $course->format = clean_param($course->format, PARAM_ALPHA);
355 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
356 * </code>
358 * @uses $CFG
359 * @uses PARAM_RAW
360 * @uses PARAM_CLEAN
361 * @uses PARAM_CLEANHTML
362 * @uses PARAM_INT
363 * @uses PARAM_NUMBER
364 * @uses PARAM_ALPHA
365 * @uses PARAM_ALPHANUM
366 * @uses PARAM_ALPHAEXT
367 * @uses PARAM_SEQUENCE
368 * @uses PARAM_BOOL
369 * @uses PARAM_NOTAGS
370 * @uses PARAM_TEXT
371 * @uses PARAM_SAFEDIR
372 * @uses PARAM_CLEANFILE
373 * @uses PARAM_FILE
374 * @uses PARAM_PATH
375 * @uses PARAM_HOST
376 * @uses PARAM_URL
377 * @uses PARAM_LOCALURL
378 * @uses PARAM_PEM
379 * @uses PARAM_BASE64
380 * @uses PARAM_TAG
381 * @uses PARAM_SEQUENCE
382 * @param mixed $param the variable we are cleaning
383 * @param int $type expected format of param after cleaning.
384 * @return mixed
386 function clean_param($param, $type) {
388 global $CFG;
390 if (is_array($param)) { // Let's loop
391 $newparam = array();
392 foreach ($param as $key => $value) {
393 $newparam[$key] = clean_param($value, $type);
395 return $newparam;
398 switch ($type) {
399 case PARAM_RAW: // no cleaning at all
400 return $param;
402 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
403 if (is_numeric($param)) {
404 return $param;
406 $param = stripslashes($param); // Needed for kses to work fine
407 $param = clean_text($param); // Sweep for scripts, etc
408 return addslashes($param); // Restore original request parameter slashes
410 case PARAM_CLEANHTML: // prepare html fragment for display, do not store it into db!!
411 $param = stripslashes($param); // Remove any slashes
412 $param = clean_text($param); // Sweep for scripts, etc
413 return trim($param);
415 case PARAM_INT:
416 return (int)$param; // Convert to integer
418 case PARAM_NUMBER:
419 return (float)$param; // Convert to integer
421 case PARAM_ALPHA: // Remove everything not a-z
422 return eregi_replace('[^a-zA-Z]', '', $param);
424 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
425 return eregi_replace('[^A-Za-z0-9]', '', $param);
427 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z/_-
428 return eregi_replace('[^a-zA-Z/_-]', '', $param);
430 case PARAM_SEQUENCE: // Remove everything not 0-9,
431 return eregi_replace('[^0-9,]', '', $param);
433 case PARAM_BOOL: // Convert to 1 or 0
434 $tempstr = strtolower($param);
435 if ($tempstr == 'on' or $tempstr == 'yes' ) {
436 $param = 1;
437 } else if ($tempstr == 'off' or $tempstr == 'no') {
438 $param = 0;
439 } else {
440 $param = empty($param) ? 0 : 1;
442 return $param;
444 case PARAM_NOTAGS: // Strip all tags
445 return strip_tags($param);
447 case PARAM_TEXT: // leave only tags needed for multilang
448 return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
450 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
451 return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
453 case PARAM_CLEANFILE: // allow only safe characters
454 return clean_filename($param);
456 case PARAM_FILE: // Strip all suspicious characters from filename
457 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
458 $param = ereg_replace('\.\.+', '', $param);
459 if($param == '.') {
460 $param = '';
462 return $param;
464 case PARAM_PATH: // Strip all suspicious characters from file path
465 $param = str_replace('\\\'', '\'', $param);
466 $param = str_replace('\\"', '"', $param);
467 $param = str_replace('\\', '/', $param);
468 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
469 $param = ereg_replace('\.\.+', '', $param);
470 $param = ereg_replace('//+', '/', $param);
471 return ereg_replace('/(\./)+', '/', $param);
473 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
474 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
475 // match ipv4 dotted quad
476 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
477 // confirm values are ok
478 if ( $match[0] > 255
479 || $match[1] > 255
480 || $match[3] > 255
481 || $match[4] > 255 ) {
482 // hmmm, what kind of dotted quad is this?
483 $param = '';
485 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
486 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
487 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
489 // all is ok - $param is respected
490 } else {
491 // all is not ok...
492 $param='';
494 return $param;
496 case PARAM_URL: // allow safe ftp, http, mailto urls
497 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
498 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
499 // all is ok, param is respected
500 } else {
501 $param =''; // not really ok
503 return $param;
505 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
506 $param = clean_param($param, PARAM_URL);
507 if (!empty($param)) {
508 if (preg_match(':^/:', $param)) {
509 // root-relative, ok!
510 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
511 // absolute, and matches our wwwroot
512 } else {
513 // relative - let's make sure there are no tricks
514 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
515 // looks ok.
516 } else {
517 $param = '';
521 return $param;
523 case PARAM_PEM:
524 $param = trim($param);
525 // PEM formatted strings may contain letters/numbers and the symbols
526 // forward slash: /
527 // plus sign: +
528 // equal sign: =
529 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
530 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
531 list($wholething, $body) = $matches;
532 unset($wholething, $matches);
533 $b64 = clean_param($body, PARAM_BASE64);
534 if (!empty($b64)) {
535 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
536 } else {
537 return '';
540 return '';
542 case PARAM_BASE64:
543 if (!empty($param)) {
544 // PEM formatted strings may contain letters/numbers and the symbols
545 // forward slash: /
546 // plus sign: +
547 // equal sign: =
548 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
549 return '';
551 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
552 // Each line of base64 encoded data must be 64 characters in
553 // length, except for the last line which may be less than (or
554 // equal to) 64 characters long.
555 for ($i=0, $j=count($lines); $i < $j; $i++) {
556 if ($i + 1 == $j) {
557 if (64 < strlen($lines[$i])) {
558 return '';
560 continue;
563 if (64 != strlen($lines[$i])) {
564 return '';
567 return implode("\n",$lines);
568 } else {
569 return '';
572 case PARAM_TAG:
573 //as long as magic_quotes_gpc is used, a backslash will be a
574 //problem, so remove *all* backslash.
575 $param = str_replace('\\', '', $param);
576 //convert many whitespace chars into one
577 $param = preg_replace('/\s+/', ' ', $param);
578 $textlib = textlib_get_instance();
579 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
580 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");
606 * Set a key in global configuration
608 * Set a key/value pair in both this session's {@link $CFG} global variable
609 * and in the 'config' database table for future sessions.
611 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
612 * In that case it doesn't affect $CFG.
614 * A NULL value will delete the entry.
616 * @param string $name the key to set
617 * @param string $value the value to set (without magic quotes)
618 * @param string $plugin (optional) the plugin scope
619 * @uses $CFG
620 * @return bool
622 function set_config($name, $value, $plugin=NULL) {
623 /// No need for get_config because they are usually always available in $CFG
625 global $CFG;
627 if (empty($plugin)) {
628 if (!array_key_exists($name, $CFG->config_php_settings)) {
629 // So it's defined for this invocation at least
630 if (is_null($value)) {
631 unset($CFG->$name);
632 } else {
633 $CFG->$name = (string)$value; // settings from db are always strings
637 if (get_field('config', 'name', 'name', $name)) {
638 if ($value===null) {
639 return delete_records('config', 'name', $name);
640 } else {
641 return set_field('config', 'value', addslashes($value), 'name', $name);
643 } else {
644 if ($value===null) {
645 return true;
647 $config = new object();
648 $config->name = $name;
649 $config->value = addslashes($value);
650 return insert_record('config', $config);
652 } else { // plugin scope
653 if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
654 if ($value===null) {
655 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
656 } else {
657 return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
659 } else {
660 if ($value===null) {
661 return true;
663 $config = new object();
664 $config->plugin = addslashes($plugin);
665 $config->name = $name;
666 $config->value = addslashes($value);
667 return insert_record('config_plugins', $config);
673 * Get configuration values from the global config table
674 * or the config_plugins table.
676 * If called with no parameters it will do the right thing
677 * generating $CFG safely from the database without overwriting
678 * existing values.
680 * If called with 2 parameters it will return a $string single
681 * value or false of the value is not found.
683 * @param string $plugin
684 * @param string $name
685 * @uses $CFG
686 * @return hash-like object or single value
689 function get_config($plugin=NULL, $name=NULL) {
691 global $CFG;
693 if (!empty($name)) { // the user is asking for a specific value
694 if (!empty($plugin)) {
695 return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
696 } else {
697 return get_field('config', 'value', 'name', $name);
701 // the user is after a recordset
702 if (!empty($plugin)) {
703 if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
704 $configs = (array)$configs;
705 $localcfg = array();
706 foreach ($configs as $config) {
707 $localcfg[$config->name] = $config->value;
709 return (object)$localcfg;
710 } else {
711 return false;
713 } else {
714 // this was originally in setup.php
715 if ($configs = get_records('config')) {
716 $localcfg = (array)$CFG;
717 foreach ($configs as $config) {
718 if (!isset($localcfg[$config->name])) {
719 $localcfg[$config->name] = $config->value;
721 // do not complain anymore if config.php overrides settings from db
724 $localcfg = (object)$localcfg;
725 return $localcfg;
726 } else {
727 // preserve $CFG if DB returns nothing or error
728 return $CFG;
735 * Removes a key from global configuration
737 * @param string $name the key to set
738 * @param string $plugin (optional) the plugin scope
739 * @uses $CFG
740 * @return bool
742 function unset_config($name, $plugin=NULL) {
744 global $CFG;
746 unset($CFG->$name);
748 if (empty($plugin)) {
749 return delete_records('config', 'name', $name);
750 } else {
751 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
756 * Get volatile flags
758 * @param string $type
759 * @param int $changedsince
760 * @return records array
763 function get_cache_flags($type, $changedsince=NULL) {
765 $type = addslashes($type);
767 $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
768 if ($changedsince !== NULL) {
769 $changedsince = (int)$changedsince;
770 $sqlwhere .= ' AND timemodified > ' . $changedsince;
772 $cf = array();
773 if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
774 foreach ($flags as $flag) {
775 $cf[$flag->name] = $flag->value;
778 return $cf;
782 * Get volatile flags
784 * @param string $type
785 * @param string $name
786 * @param int $changedsince
787 * @return records array
790 function get_cache_flag($type, $name, $changedsince=NULL) {
792 $type = addslashes($type);
793 $name = addslashes($name);
795 $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
796 if ($changedsince !== NULL) {
797 $changedsince = (int)$changedsince;
798 $sqlwhere .= ' AND timemodified > ' . $changedsince;
800 return get_field_select('cache_flags', 'value', $sqlwhere);
804 * Set a volatile flag
806 * @param string $type the "type" namespace for the key
807 * @param string $name the key to set
808 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
809 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
810 * @return bool
812 function set_cache_flag($type, $name, $value, $expiry=NULL) {
815 $timemodified = time();
816 if ($expiry===NULL || $expiry < $timemodified) {
817 $expiry = $timemodified + 24 * 60 * 60;
818 } else {
819 $expiry = (int)$expiry;
822 if ($value === NULL) {
823 return unset_cache_flag($type,$name);
826 $type = addslashes($type);
827 $name = addslashes($name);
828 if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
829 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
830 return true; //no need to update; helps rcache too
832 $f->value = addslashes($value);
833 $f->expiry = $expiry;
834 $f->timemodified = $timemodified;
835 return update_record('cache_flags', $f);
836 } else {
837 $f = new object();
838 $f->flagtype = $type;
839 $f->name = $name;
840 $f->value = addslashes($value);
841 $f->expiry = $expiry;
842 $f->timemodified = $timemodified;
843 return (bool)insert_record('cache_flags', $f);
848 * Removes a single volatile flag
850 * @param string $type the "type" namespace for the key
851 * @param string $name the key to set
852 * @uses $CFG
853 * @return bool
855 function unset_cache_flag($type, $name) {
857 return delete_records('cache_flags',
858 'name', addslashes($name),
859 'flagtype', addslashes($type));
863 * Garbage-collect volatile flags
866 function gc_cache_flags() {
867 return delete_records_select('cache_flags', 'expiry < ' . time());
871 * Refresh current $USER session global variable with all their current preferences.
872 * @uses $USER
874 function reload_user_preferences() {
876 global $USER;
878 //reset preference
879 $USER->preference = array();
881 if (!isloggedin() or isguestuser()) {
882 // no permanent storage for not-logged-in user and guest
884 } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
885 foreach ($preferences as $preference) {
886 $USER->preference[$preference->name] = $preference->value;
890 return true;
894 * Sets a preference for the current user
895 * Optionally, can set a preference for a different user object
896 * @uses $USER
897 * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
899 * @param string $name The key to set as preference for the specified user
900 * @param string $value The value to set forthe $name key in the specified user's record
901 * @param int $otheruserid A moodle user ID
902 * @return bool
904 function set_user_preference($name, $value, $otheruserid=NULL) {
906 global $USER;
908 if (!isset($USER->preference)) {
909 reload_user_preferences();
912 if (empty($name)) {
913 return false;
916 $nostore = false;
918 if (empty($otheruserid)){
919 if (!isloggedin() or isguestuser()) {
920 $nostore = true;
922 $userid = $USER->id;
923 } else {
924 if (isguestuser($otheruserid)) {
925 $nostore = true;
927 $userid = $otheruserid;
930 $return = true;
931 if ($nostore) {
932 // no permanent storage for not-logged-in user and guest
934 } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
935 if ($preference->value === $value) {
936 return true;
938 if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
939 $return = false;
942 } else {
943 $preference = new object();
944 $preference->userid = $userid;
945 $preference->name = addslashes($name);
946 $preference->value = addslashes((string)$value);
947 if (!insert_record('user_preferences', $preference)) {
948 $return = false;
952 // update value in USER session if needed
953 if ($userid == $USER->id) {
954 $USER->preference[$name] = (string)$value;
957 return $return;
961 * Unsets a preference completely by deleting it from the database
962 * Optionally, can set a preference for a different user id
963 * @uses $USER
964 * @param string $name The key to unset as preference for the specified user
965 * @param int $otheruserid A moodle user ID
967 function unset_user_preference($name, $otheruserid=NULL) {
969 global $USER;
971 if (!isset($USER->preference)) {
972 reload_user_preferences();
975 if (empty($otheruserid)){
976 $userid = $USER->id;
977 } else {
978 $userid = $otheruserid;
981 //Delete the preference from $USER if needed
982 if ($userid == $USER->id) {
983 unset($USER->preference[$name]);
986 //Then from DB
987 return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
992 * Sets a whole array of preferences for the current user
993 * @param array $prefarray An array of key/value pairs to be set
994 * @param int $otheruserid A moodle user ID
995 * @return bool
997 function set_user_preferences($prefarray, $otheruserid=NULL) {
999 if (!is_array($prefarray) or empty($prefarray)) {
1000 return false;
1003 $return = true;
1004 foreach ($prefarray as $name => $value) {
1005 // The order is important; test for return is done first
1006 $return = (set_user_preference($name, $value, $otheruserid) && $return);
1008 return $return;
1012 * If no arguments are supplied this function will return
1013 * all of the current user preferences as an array.
1014 * If a name is specified then this function
1015 * attempts to return that particular preference value. If
1016 * none is found, then the optional value $default is returned,
1017 * otherwise NULL.
1018 * @param string $name Name of the key to use in finding a preference value
1019 * @param string $default Value to be returned if the $name key is not set in the user preferences
1020 * @param int $otheruserid A moodle user ID
1021 * @uses $USER
1022 * @return string
1024 function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
1025 global $USER;
1027 if (!isset($USER->preference)) {
1028 reload_user_preferences();
1031 if (empty($otheruserid)){
1032 $userid = $USER->id;
1033 } else {
1034 $userid = $otheruserid;
1037 if ($userid == $USER->id) {
1038 $preference = $USER->preference;
1040 } else {
1041 $preference = array();
1042 if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
1043 foreach ($prefdata as $pref) {
1044 $preference[$pref->name] = $pref->value;
1049 if (empty($name)) {
1050 return $preference; // All values
1052 } else if (array_key_exists($name, $preference)) {
1053 return $preference[$name]; // The single value
1055 } else {
1056 return $default; // Default value (or NULL)
1061 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1064 * Given date parts in user time produce a GMT timestamp.
1066 * @param int $year The year part to create timestamp of
1067 * @param int $month The month part to create timestamp of
1068 * @param int $day The day part to create timestamp of
1069 * @param int $hour The hour part to create timestamp of
1070 * @param int $minute The minute part to create timestamp of
1071 * @param int $second The second part to create timestamp of
1072 * @param float $timezone ?
1073 * @param bool $applydst ?
1074 * @return int timestamp
1075 * @todo Finish documenting this function
1077 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1079 $strtimezone = NULL;
1080 if (!is_numeric($timezone)) {
1081 $strtimezone = $timezone;
1084 $timezone = get_user_timezone_offset($timezone);
1086 if (abs($timezone) > 13) {
1087 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1088 } else {
1089 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1090 $time = usertime($time, $timezone);
1091 if($applydst) {
1092 $time -= dst_offset_on($time, $strtimezone);
1096 return $time;
1101 * Given an amount of time in seconds, returns string
1102 * formatted nicely as weeks, days, hours etc as needed
1104 * @uses MINSECS
1105 * @uses HOURSECS
1106 * @uses DAYSECS
1107 * @uses YEARSECS
1108 * @param int $totalsecs ?
1109 * @param array $str ?
1110 * @return string
1112 function format_time($totalsecs, $str=NULL) {
1114 $totalsecs = abs($totalsecs);
1116 if (!$str) { // Create the str structure the slow way
1117 $str->day = get_string('day');
1118 $str->days = get_string('days');
1119 $str->hour = get_string('hour');
1120 $str->hours = get_string('hours');
1121 $str->min = get_string('min');
1122 $str->mins = get_string('mins');
1123 $str->sec = get_string('sec');
1124 $str->secs = get_string('secs');
1125 $str->year = get_string('year');
1126 $str->years = get_string('years');
1130 $years = floor($totalsecs/YEARSECS);
1131 $remainder = $totalsecs - ($years*YEARSECS);
1132 $days = floor($remainder/DAYSECS);
1133 $remainder = $totalsecs - ($days*DAYSECS);
1134 $hours = floor($remainder/HOURSECS);
1135 $remainder = $remainder - ($hours*HOURSECS);
1136 $mins = floor($remainder/MINSECS);
1137 $secs = $remainder - ($mins*MINSECS);
1139 $ss = ($secs == 1) ? $str->sec : $str->secs;
1140 $sm = ($mins == 1) ? $str->min : $str->mins;
1141 $sh = ($hours == 1) ? $str->hour : $str->hours;
1142 $sd = ($days == 1) ? $str->day : $str->days;
1143 $sy = ($years == 1) ? $str->year : $str->years;
1145 $oyears = '';
1146 $odays = '';
1147 $ohours = '';
1148 $omins = '';
1149 $osecs = '';
1151 if ($years) $oyears = $years .' '. $sy;
1152 if ($days) $odays = $days .' '. $sd;
1153 if ($hours) $ohours = $hours .' '. $sh;
1154 if ($mins) $omins = $mins .' '. $sm;
1155 if ($secs) $osecs = $secs .' '. $ss;
1157 if ($years) return trim($oyears .' '. $odays);
1158 if ($days) return trim($odays .' '. $ohours);
1159 if ($hours) return trim($ohours .' '. $omins);
1160 if ($mins) return trim($omins .' '. $osecs);
1161 if ($secs) return $osecs;
1162 return get_string('now');
1166 * Returns a formatted string that represents a date in user time
1167 * <b>WARNING: note that the format is for strftime(), not date().</b>
1168 * Because of a bug in most Windows time libraries, we can't use
1169 * the nicer %e, so we have to use %d which has leading zeroes.
1170 * A lot of the fuss in the function is just getting rid of these leading
1171 * zeroes as efficiently as possible.
1173 * If parameter fixday = true (default), then take off leading
1174 * zero from %d, else mantain it.
1176 * @uses HOURSECS
1177 * @param int $date timestamp in GMT
1178 * @param string $format strftime format
1179 * @param float $timezone
1180 * @param bool $fixday If true (default) then the leading
1181 * zero from %d is removed. If false then the leading zero is mantained.
1182 * @return string
1184 function userdate($date, $format='', $timezone=99, $fixday = true) {
1186 global $CFG;
1188 $strtimezone = NULL;
1189 if (!is_numeric($timezone)) {
1190 $strtimezone = $timezone;
1193 if (empty($format)) {
1194 $format = get_string('strftimedaydatetime');
1197 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
1198 $fixday = false;
1199 } else if ($fixday) {
1200 $formatnoday = str_replace('%d', 'DD', $format);
1201 $fixday = ($formatnoday != $format);
1204 $date += dst_offset_on($date, $strtimezone);
1206 $timezone = get_user_timezone_offset($timezone);
1208 if (abs($timezone) > 13) { /// Server time
1209 if ($fixday) {
1210 $datestring = strftime($formatnoday, $date);
1211 $daystring = str_replace(' 0', '', strftime(' %d', $date));
1212 $datestring = str_replace('DD', $daystring, $datestring);
1213 } else {
1214 $datestring = strftime($format, $date);
1216 } else {
1217 $date += (int)($timezone * 3600);
1218 if ($fixday) {
1219 $datestring = gmstrftime($formatnoday, $date);
1220 $daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
1221 $datestring = str_replace('DD', $daystring, $datestring);
1222 } else {
1223 $datestring = gmstrftime($format, $date);
1227 /// If we are running under Windows convert from windows encoding to UTF-8
1228 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1230 if ($CFG->ostype == 'WINDOWS') {
1231 if ($localewincharset = get_string('localewincharset')) {
1232 $textlib = textlib_get_instance();
1233 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1237 return $datestring;
1241 * Given a $time timestamp in GMT (seconds since epoch),
1242 * returns an array that represents the date in user time
1244 * @uses HOURSECS
1245 * @param int $time Timestamp in GMT
1246 * @param float $timezone ?
1247 * @return array An array that represents the date in user time
1248 * @todo Finish documenting this function
1250 function usergetdate($time, $timezone=99) {
1252 $strtimezone = NULL;
1253 if (!is_numeric($timezone)) {
1254 $strtimezone = $timezone;
1257 $timezone = get_user_timezone_offset($timezone);
1259 if (abs($timezone) > 13) { // Server time
1260 return getdate($time);
1263 // There is no gmgetdate so we use gmdate instead
1264 $time += dst_offset_on($time, $strtimezone);
1265 $time += intval((float)$timezone * HOURSECS);
1267 $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
1269 list(
1270 $getdate['seconds'],
1271 $getdate['minutes'],
1272 $getdate['hours'],
1273 $getdate['mday'],
1274 $getdate['mon'],
1275 $getdate['year'],
1276 $getdate['wday'],
1277 $getdate['yday'],
1278 $getdate['weekday'],
1279 $getdate['month']
1280 ) = explode('_', $datestring);
1282 return $getdate;
1286 * Given a GMT timestamp (seconds since epoch), offsets it by
1287 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1289 * @uses HOURSECS
1290 * @param int $date Timestamp in GMT
1291 * @param float $timezone
1292 * @return int
1294 function usertime($date, $timezone=99) {
1296 $timezone = get_user_timezone_offset($timezone);
1298 if (abs($timezone) > 13) {
1299 return $date;
1301 return $date - (int)($timezone * HOURSECS);
1305 * Given a time, return the GMT timestamp of the most recent midnight
1306 * for the current user.
1308 * @param int $date Timestamp in GMT
1309 * @param float $timezone ?
1310 * @return ?
1312 function usergetmidnight($date, $timezone=99) {
1314 $userdate = usergetdate($date, $timezone);
1316 // Time of midnight of this user's day, in GMT
1317 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1322 * Returns a string that prints the user's timezone
1324 * @param float $timezone The user's timezone
1325 * @return string
1327 function usertimezone($timezone=99) {
1329 $tz = get_user_timezone($timezone);
1331 if (!is_float($tz)) {
1332 return $tz;
1335 if(abs($tz) > 13) { // Server time
1336 return get_string('serverlocaltime');
1339 if($tz == intval($tz)) {
1340 // Don't show .0 for whole hours
1341 $tz = intval($tz);
1344 if($tz == 0) {
1345 return 'UTC';
1347 else if($tz > 0) {
1348 return 'UTC+'.$tz;
1350 else {
1351 return 'UTC'.$tz;
1357 * Returns a float which represents the user's timezone difference from GMT in hours
1358 * Checks various settings and picks the most dominant of those which have a value
1360 * @uses $CFG
1361 * @uses $USER
1362 * @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
1363 * @return int
1365 function get_user_timezone_offset($tz = 99) {
1367 global $USER, $CFG;
1369 $tz = get_user_timezone($tz);
1371 if (is_float($tz)) {
1372 return $tz;
1373 } else {
1374 $tzrecord = get_timezone_record($tz);
1375 if (empty($tzrecord)) {
1376 return 99.0;
1378 return (float)$tzrecord->gmtoff / HOURMINS;
1383 * Returns an int which represents the systems's timezone difference from GMT in seconds
1384 * @param mixed $tz timezone
1385 * @return int if found, false is timezone 99 or error
1387 function get_timezone_offset($tz) {
1388 global $CFG;
1390 if ($tz == 99) {
1391 return false;
1394 if (is_numeric($tz)) {
1395 return intval($tz * 60*60);
1398 if (!$tzrecord = get_timezone_record($tz)) {
1399 return false;
1401 return intval($tzrecord->gmtoff * 60);
1405 * Returns a float or a string which denotes the user's timezone
1406 * 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)
1407 * means that for this timezone there are also DST rules to be taken into account
1408 * Checks various settings and picks the most dominant of those which have a value
1410 * @uses $USER
1411 * @uses $CFG
1412 * @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
1413 * @return mixed
1415 function get_user_timezone($tz = 99) {
1416 global $USER, $CFG;
1418 $timezones = array(
1419 $tz,
1420 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1421 isset($USER->timezone) ? $USER->timezone : 99,
1422 isset($CFG->timezone) ? $CFG->timezone : 99,
1425 $tz = 99;
1427 while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
1428 $tz = $next['value'];
1431 return is_numeric($tz) ? (float) $tz : $tz;
1437 * @uses $CFG
1438 * @uses $db
1439 * @param string $timezonename ?
1440 * @return object
1442 function get_timezone_record($timezonename) {
1443 global $CFG, $db;
1444 static $cache = NULL;
1446 if ($cache === NULL) {
1447 $cache = array();
1450 if (isset($cache[$timezonename])) {
1451 return $cache[$timezonename];
1454 return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1455 WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1461 * @uses $CFG
1462 * @uses $USER
1463 * @param ? $fromyear ?
1464 * @param ? $to_year ?
1465 * @return bool
1467 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1468 global $CFG, $SESSION;
1470 $usertz = get_user_timezone($strtimezone);
1472 if (is_float($usertz)) {
1473 // Trivial timezone, no DST
1474 return false;
1477 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1478 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1479 unset($SESSION->dst_offsets);
1480 unset($SESSION->dst_range);
1483 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1484 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1485 // This will be the return path most of the time, pretty light computationally
1486 return true;
1489 // Reaching here means we either need to extend our table or create it from scratch
1491 // Remember which TZ we calculated these changes for
1492 $SESSION->dst_offsettz = $usertz;
1494 if(empty($SESSION->dst_offsets)) {
1495 // If we 're creating from scratch, put the two guard elements in there
1496 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1498 if(empty($SESSION->dst_range)) {
1499 // If creating from scratch
1500 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1501 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
1503 // Fill in the array with the extra years we need to process
1504 $yearstoprocess = array();
1505 for($i = $from; $i <= $to; ++$i) {
1506 $yearstoprocess[] = $i;
1509 // Take note of which years we have processed for future calls
1510 $SESSION->dst_range = array($from, $to);
1512 else {
1513 // If needing to extend the table, do the same
1514 $yearstoprocess = array();
1516 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1517 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
1519 if($from < $SESSION->dst_range[0]) {
1520 // Take note of which years we need to process and then note that we have processed them for future calls
1521 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1522 $yearstoprocess[] = $i;
1524 $SESSION->dst_range[0] = $from;
1526 if($to > $SESSION->dst_range[1]) {
1527 // Take note of which years we need to process and then note that we have processed them for future calls
1528 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1529 $yearstoprocess[] = $i;
1531 $SESSION->dst_range[1] = $to;
1535 if(empty($yearstoprocess)) {
1536 // This means that there was a call requesting a SMALLER range than we have already calculated
1537 return true;
1540 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1541 // Also, the array is sorted in descending timestamp order!
1543 // Get DB data
1545 static $presets_cache = array();
1546 if (!isset($presets_cache[$usertz])) {
1547 $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');
1549 if(empty($presets_cache[$usertz])) {
1550 return false;
1553 // Remove ending guard (first element of the array)
1554 reset($SESSION->dst_offsets);
1555 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1557 // Add all required change timestamps
1558 foreach($yearstoprocess as $y) {
1559 // Find the record which is in effect for the year $y
1560 foreach($presets_cache[$usertz] as $year => $preset) {
1561 if($year <= $y) {
1562 break;
1566 $changes = dst_changes_for_year($y, $preset);
1568 if($changes === NULL) {
1569 continue;
1571 if($changes['dst'] != 0) {
1572 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1574 if($changes['std'] != 0) {
1575 $SESSION->dst_offsets[$changes['std']] = 0;
1579 // Put in a guard element at the top
1580 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1581 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1583 // Sort again
1584 krsort($SESSION->dst_offsets);
1586 return true;
1589 function dst_changes_for_year($year, $timezone) {
1591 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1592 return NULL;
1595 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1596 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1598 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1599 list($std_hour, $std_min) = explode(':', $timezone->std_time);
1601 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1602 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1604 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1605 // This has the advantage of being able to have negative values for hour, i.e. for timezones
1606 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1608 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1609 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1611 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1614 // $time must NOT be compensated at all, it has to be a pure timestamp
1615 function dst_offset_on($time, $strtimezone = NULL) {
1616 global $SESSION;
1618 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
1619 return 0;
1622 reset($SESSION->dst_offsets);
1623 while(list($from, $offset) = each($SESSION->dst_offsets)) {
1624 if($from <= $time) {
1625 break;
1629 // This is the normal return path
1630 if($offset !== NULL) {
1631 return $offset;
1634 // Reaching this point means we haven't calculated far enough, do it now:
1635 // Calculate extra DST changes if needed and recurse. The recursion always
1636 // moves toward the stopping condition, so will always end.
1638 if($from == 0) {
1639 // We need a year smaller than $SESSION->dst_range[0]
1640 if($SESSION->dst_range[0] == 1971) {
1641 return 0;
1643 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
1644 return dst_offset_on($time, $strtimezone);
1646 else {
1647 // We need a year larger than $SESSION->dst_range[1]
1648 if($SESSION->dst_range[1] == 2035) {
1649 return 0;
1651 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
1652 return dst_offset_on($time, $strtimezone);
1656 function find_day_in_month($startday, $weekday, $month, $year) {
1658 $daysinmonth = days_in_month($month, $year);
1660 if($weekday == -1) {
1661 // Don't care about weekday, so return:
1662 // abs($startday) if $startday != -1
1663 // $daysinmonth otherwise
1664 return ($startday == -1) ? $daysinmonth : abs($startday);
1667 // From now on we 're looking for a specific weekday
1669 // Give "end of month" its actual value, since we know it
1670 if($startday == -1) {
1671 $startday = -1 * $daysinmonth;
1674 // Starting from day $startday, the sign is the direction
1676 if($startday < 1) {
1678 $startday = abs($startday);
1679 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1681 // This is the last such weekday of the month
1682 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1683 if($lastinmonth > $daysinmonth) {
1684 $lastinmonth -= 7;
1687 // Find the first such weekday <= $startday
1688 while($lastinmonth > $startday) {
1689 $lastinmonth -= 7;
1692 return $lastinmonth;
1695 else {
1697 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1699 $diff = $weekday - $indexweekday;
1700 if($diff < 0) {
1701 $diff += 7;
1704 // This is the first such weekday of the month equal to or after $startday
1705 $firstfromindex = $startday + $diff;
1707 return $firstfromindex;
1713 * Calculate the number of days in a given month
1715 * @param int $month The month whose day count is sought
1716 * @param int $year The year of the month whose day count is sought
1717 * @return int
1719 function days_in_month($month, $year) {
1720 return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1724 * Calculate the position in the week of a specific calendar day
1726 * @param int $day The day of the date whose position in the week is sought
1727 * @param int $month The month of the date whose position in the week is sought
1728 * @param int $year The year of the date whose position in the week is sought
1729 * @return int
1731 function dayofweek($day, $month, $year) {
1732 // I wonder if this is any different from
1733 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1734 return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1737 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1740 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1741 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1742 * sesskey string if $USER exists, or boolean false if not.
1744 * @uses $USER
1745 * @return string
1747 function sesskey() {
1748 global $USER;
1750 if(!isset($USER)) {
1751 return false;
1754 if (empty($USER->sesskey)) {
1755 $USER->sesskey = random_string(10);
1758 return $USER->sesskey;
1763 * For security purposes, this function will check that the currently
1764 * given sesskey (passed as a parameter to the script or this function)
1765 * matches that of the current user.
1767 * @param string $sesskey optionally provided sesskey
1768 * @return bool
1770 function confirm_sesskey($sesskey=NULL) {
1771 global $USER;
1773 if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1774 return true;
1777 if (empty($sesskey)) {
1778 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
1781 if (!isset($USER->sesskey)) {
1782 return false;
1785 return ($USER->sesskey === $sesskey);
1789 * Setup all global $CFG course variables, set locale and also themes
1790 * This function can be used on pages that do not require login instead of require_login()
1792 * @param mixed $courseorid id of the course or course object
1794 function course_setup($courseorid=0) {
1795 global $COURSE, $CFG, $SITE;
1797 /// Redefine global $COURSE if needed
1798 if (empty($courseorid)) {
1799 // no change in global $COURSE - for backwards compatibiltiy
1800 // if require_rogin() used after require_login($courseid);
1801 } else if (is_object($courseorid)) {
1802 $COURSE = clone($courseorid);
1803 } else {
1804 global $course; // used here only to prevent repeated fetching from DB - may be removed later
1805 if ($courseorid == SITEID) {
1806 $COURSE = clone($SITE);
1807 } else if (!empty($course->id) and $course->id == $courseorid) {
1808 $COURSE = clone($course);
1809 } else {
1810 if (!$COURSE = get_record('course', 'id', $courseorid)) {
1811 error('Invalid course ID');
1816 /// set locale and themes
1817 moodle_setlocale();
1818 theme_setup();
1823 * This function checks that the current user is logged in and has the
1824 * required privileges
1826 * This function checks that the current user is logged in, and optionally
1827 * whether they are allowed to be in a particular course and view a particular
1828 * course module.
1829 * If they are not logged in, then it redirects them to the site login unless
1830 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1831 * case they are automatically logged in as guests.
1832 * If $courseid is given and the user is not enrolled in that course then the
1833 * user is redirected to the course enrolment page.
1834 * If $cm is given and the coursemodule is hidden and the user is not a teacher
1835 * in the course then the user is redirected to the course home page.
1837 * @uses $CFG
1838 * @uses $SESSION
1839 * @uses $USER
1840 * @uses $FULLME
1841 * @uses SITEID
1842 * @uses $COURSE
1843 * @param mixed $courseorid id of the course or course object
1844 * @param bool $autologinguest
1845 * @param object $cm course module object
1846 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
1847 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
1848 * in order to keep redirects working properly. MDL-14495
1850 function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
1852 global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1854 /// setup global $COURSE, themes, language and locale
1855 course_setup($courseorid);
1857 /// If the user is not even logged in yet then make sure they are
1858 if (!isloggedin()) {
1859 //NOTE: $USER->site check was obsoleted by session test cookie,
1860 // $USER->confirmed test is in login/index.php
1861 if ($setwantsurltome) {
1862 $SESSION->wantsurl = $FULLME;
1864 if (!empty($_SERVER['HTTP_REFERER'])) {
1865 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
1867 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1868 $loginguest = '?loginguest=true';
1869 } else {
1870 $loginguest = '';
1872 if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
1873 redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1874 } else {
1875 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1876 redirect($wwwroot .'/login/index.php');
1878 exit;
1881 /// loginas as redirection if needed
1882 if ($COURSE->id != SITEID and !empty($USER->realuser)) {
1883 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
1884 if ($USER->loginascontext->instanceid != $COURSE->id) {
1885 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
1890 /// check whether the user should be changing password (but only if it is REALLY them)
1891 if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
1892 $userauth = get_auth_plugin($USER->auth);
1893 if ($userauth->can_change_password()) {
1894 $SESSION->wantsurl = $FULLME;
1895 if ($changeurl = $userauth->change_password_url()) {
1896 //use plugin custom url
1897 redirect($changeurl);
1898 } else {
1899 //use moodle internal method
1900 if (empty($CFG->loginhttps)) {
1901 redirect($CFG->wwwroot .'/login/change_password.php');
1902 } else {
1903 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1904 redirect($wwwroot .'/login/change_password.php');
1907 } else {
1908 print_error('nopasswordchangeforced', 'auth');
1912 /// Check that the user account is properly set up
1913 if (user_not_fully_set_up($USER)) {
1914 $SESSION->wantsurl = $FULLME;
1915 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1918 /// Make sure current IP matches the one for this session (if required)
1919 if (!empty($CFG->tracksessionip)) {
1920 if ($USER->sessionIP != md5(getremoteaddr())) {
1921 print_error('sessionipnomatch', 'error');
1925 /// Make sure the USER has a sesskey set up. Used for checking script parameters.
1926 sesskey();
1928 // Check that the user has agreed to a site policy if there is one
1929 if (!empty($CFG->sitepolicy)) {
1930 if (!$USER->policyagreed) {
1931 $SESSION->wantsurl = $FULLME;
1932 redirect($CFG->wwwroot .'/user/policy.php');
1936 // Fetch the system context, we are going to use it a lot.
1937 $sysctx = get_context_instance(CONTEXT_SYSTEM);
1939 /// If the site is currently under maintenance, then print a message
1940 if (!has_capability('moodle/site:config', $sysctx)) {
1941 if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1942 print_maintenance_message();
1943 exit;
1947 /// groupmembersonly access control
1948 if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
1949 if (isguestuser() or !groups_has_membership($cm)) {
1950 print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
1954 // Fetch the course context, and prefetch its child contexts
1955 if (!isset($COURSE->context)) {
1956 if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
1957 print_error('nocontext');
1960 if ($COURSE->id == SITEID) {
1961 /// Eliminate hidden site activities straight away
1962 if (!empty($cm) && !$cm->visible
1963 && !has_capability('moodle/course:viewhiddenactivities', $COURSE->context)) {
1964 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
1966 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
1967 return;
1969 } else {
1971 /// Check if the user can be in a particular course
1972 if (empty($USER->access['rsw'][$COURSE->context->path])) {
1974 // MDL-13900 - If the course or the parent category are hidden
1975 // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
1977 if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
1978 !has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
1979 print_header_simple();
1980 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
1984 /// Non-guests who don't currently have access, check if they can be allowed in as a guest
1986 if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
1987 if ($COURSE->guest == 1) {
1988 // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
1989 $USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
1993 /// If the user is a guest then treat them according to the course policy about guests
1995 if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
1996 if (has_capability('moodle/site:doanything', $sysctx)) {
1997 // administrators must be able to access any course - even if somebody gives them guest access
1998 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
1999 return;
2002 switch ($COURSE->guest) { /// Check course policy about guest access
2004 case 1: /// Guests always allowed
2005 if (!has_capability('moodle/course:view', $COURSE->context)) { // Prohibited by capability
2006 print_header_simple();
2007 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2009 if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
2010 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
2011 get_string('activityiscurrentlyhidden'));
2014 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2015 return; // User is allowed to see this course
2017 break;
2019 case 2: /// Guests allowed with key
2020 if (!empty($USER->enrolkey[$COURSE->id])) { // Set by enrol/manual/enrol.php
2021 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2022 return true;
2024 // otherwise drop through to logic below (--> enrol.php)
2025 break;
2027 default: /// Guests not allowed
2028 $strloggedinasguest = get_string('loggedinasguest');
2029 print_header_simple('', '',
2030 build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
2031 if (empty($USER->access['rsw'][$COURSE->context->path])) { // Normal guest
2032 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2033 } else {
2034 notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
2035 echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
2036 print_footer($COURSE);
2037 exit;
2039 break;
2042 /// For non-guests, check if they have course view access
2044 } else if (has_capability('moodle/course:view', $COURSE->context)) {
2045 if (!empty($USER->realuser)) { // Make sure the REAL person can also access this course
2046 if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
2047 print_header_simple();
2048 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2052 /// Make sure they can read this activity too, if specified
2054 if (!empty($cm) and !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $COURSE->context)) {
2055 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
2057 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2058 return; // User is allowed to see this course
2063 /// Currently not enrolled in the course, so see if they want to enrol
2064 $SESSION->wantsurl = $FULLME;
2065 redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
2066 die;
2073 * This function just makes sure a user is logged out.
2075 * @uses $CFG
2076 * @uses $USER
2078 function require_logout() {
2080 global $USER, $CFG, $SESSION;
2082 if (isloggedin()) {
2083 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2085 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2086 foreach($authsequence as $authname) {
2087 $authplugin = get_auth_plugin($authname);
2088 $authplugin->prelogout_hook();
2092 if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
2093 // This method is just to try to avoid silly warnings from PHP 4.3.0
2094 session_unregister("USER");
2095 session_unregister("SESSION");
2098 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
2099 $file = $line = null;
2100 if (headers_sent($file, $line)) {
2101 error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
2102 error_log('Headers were already sent in file: '.$file.' on line '.$line);
2103 } else {
2104 if (check_php_version('5.2.0')) {
2105 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly);
2106 } else {
2107 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure);
2111 unset($_SESSION['USER']);
2112 unset($_SESSION['SESSION']);
2114 unset($SESSION);
2115 unset($USER);
2120 * This is a weaker version of {@link require_login()} which only requires login
2121 * when called from within a course rather than the site page, unless
2122 * the forcelogin option is turned on.
2124 * @uses $CFG
2125 * @param mixed $courseorid The course object or id in question
2126 * @param bool $autologinguest Allow autologin guests if that is wanted
2127 * @param object $cm Course activity module if known
2128 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2129 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2130 * in order to keep redirects working properly. MDL-14495
2132 function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
2133 global $CFG;
2134 if (!empty($CFG->forcelogin)) {
2135 // login required for both SITE and courses
2136 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2138 } else if (!empty($cm) and !$cm->visible) {
2139 // always login for hidden activities
2140 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2142 } else if ((is_object($courseorid) and $courseorid->id == SITEID)
2143 or (!is_object($courseorid) and $courseorid == SITEID)) {
2144 //login for SITE not required
2145 user_accesstime_log(SITEID);
2146 return;
2148 } else {
2149 // course login always required
2150 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2155 * Require key login. Function terminates with error if key not found or incorrect.
2156 * @param string $script unique script identifier
2157 * @param int $instance optional instance id
2159 function require_user_key_login($script, $instance=null) {
2160 global $nomoodlecookie, $USER, $SESSION, $CFG;
2162 if (empty($nomoodlecookie)) {
2163 error('Incorrect use of require_key_login() - session cookies must be disabled!');
2166 /// extra safety
2167 @session_write_close();
2169 $keyvalue = required_param('key', PARAM_ALPHANUM);
2171 if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
2172 error('Incorrect key');
2175 if (!empty($key->validuntil) and $key->validuntil < time()) {
2176 error('Expired key');
2179 if ($key->iprestriction) {
2180 $remoteaddr = getremoteaddr();
2181 if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
2182 error('Client IP address mismatch');
2186 if (!$user = get_record('user', 'id', $key->userid)) {
2187 error('Incorrect user record');
2190 /// emulate normal session
2191 $SESSION = new object();
2192 $USER = $user;
2194 /// note we are not using normal login
2195 if (!defined('USER_KEY_LOGIN')) {
2196 define('USER_KEY_LOGIN', true);
2199 load_all_capabilities();
2201 /// return isntance id - it might be empty
2202 return $key->instance;
2206 * Creates a new private user access key.
2207 * @param string $script unique target identifier
2208 * @param int $userid
2209 * @param instance $int optional instance id
2210 * @param string $iprestriction optional ip restricted access
2211 * @param timestamp $validuntil key valid only until given data
2212 * @return string access key value
2214 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2215 $key = new object();
2216 $key->script = $script;
2217 $key->userid = $userid;
2218 $key->instance = $instance;
2219 $key->iprestriction = $iprestriction;
2220 $key->validuntil = $validuntil;
2221 $key->timecreated = time();
2223 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
2224 while (record_exists('user_private_key', 'value', $key->value)) {
2225 // must be unique
2226 $key->value = md5($userid.'_'.time().random_string(40));
2229 if (!insert_record('user_private_key', $key)) {
2230 error('Can not insert new key');
2233 return $key->value;
2237 * Modify the user table by setting the currently logged in user's
2238 * last login to now.
2240 * @uses $USER
2241 * @return bool
2243 function update_user_login_times() {
2244 global $USER;
2246 $user = new object();
2247 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
2248 $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
2250 $user->id = $USER->id;
2252 return update_record('user', $user);
2256 * Determines if a user has completed setting up their account.
2258 * @param user $user A {@link $USER} object to test for the existance of a valid name and email
2259 * @return bool
2261 function user_not_fully_set_up($user) {
2262 return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
2265 function over_bounce_threshold($user) {
2267 global $CFG;
2269 if (empty($CFG->handlebounces)) {
2270 return false;
2272 // set sensible defaults
2273 if (empty($CFG->minbounces)) {
2274 $CFG->minbounces = 10;
2276 if (empty($CFG->bounceratio)) {
2277 $CFG->bounceratio = .20;
2279 $bouncecount = 0;
2280 $sendcount = 0;
2281 if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2282 $bouncecount = $bounce->value;
2284 if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2285 $sendcount = $send->value;
2287 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
2291 * @param $user - object containing an id
2292 * @param $reset - will reset the count to 0
2294 function set_send_count($user,$reset=false) {
2295 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2296 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2297 update_record('user_preferences',$pref);
2299 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2300 // make a new one
2301 $pref->name = 'email_send_count';
2302 $pref->value = 1;
2303 $pref->userid = $user->id;
2304 insert_record('user_preferences',$pref, false);
2309 * @param $user - object containing an id
2310 * @param $reset - will reset the count to 0
2312 function set_bounce_count($user,$reset=false) {
2313 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2314 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2315 update_record('user_preferences',$pref);
2317 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2318 // make a new one
2319 $pref->name = 'email_bounce_count';
2320 $pref->value = 1;
2321 $pref->userid = $user->id;
2322 insert_record('user_preferences',$pref, false);
2327 * Keeps track of login attempts
2329 * @uses $SESSION
2331 function update_login_count() {
2333 global $SESSION;
2335 $max_logins = 10;
2337 if (empty($SESSION->logincount)) {
2338 $SESSION->logincount = 1;
2339 } else {
2340 $SESSION->logincount++;
2343 if ($SESSION->logincount > $max_logins) {
2344 unset($SESSION->wantsurl);
2345 print_error('errortoomanylogins');
2350 * Resets login attempts
2352 * @uses $SESSION
2354 function reset_login_count() {
2355 global $SESSION;
2357 $SESSION->logincount = 0;
2360 function sync_metacourses() {
2362 global $CFG;
2364 if (!$courses = get_records('course', 'metacourse', 1)) {
2365 return;
2368 foreach ($courses as $course) {
2369 sync_metacourse($course);
2374 * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2376 * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2378 function sync_metacourse($course) {
2379 global $CFG;
2381 // Check the course is valid.
2382 if (!is_object($course)) {
2383 if (!$course = get_record('course', 'id', $course)) {
2384 return false; // invalid course id
2388 // Check that we actually have a metacourse.
2389 if (empty($course->metacourse)) {
2390 return false;
2393 // Get a list of roles that should not be synced.
2394 if (!empty($CFG->nonmetacoursesyncroleids)) {
2395 $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2396 } else {
2397 $roleexclusions = '';
2400 // Get the context of the metacourse.
2401 $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2403 // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2404 if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2405 $managers = array_keys($users);
2406 } else {
2407 $managers = array();
2410 // Get assignments of a user to a role that exist in a child course, but
2411 // not in the meta coure. That is, get a list of the assignments that need to be made.
2412 if (!$assignments = get_records_sql("
2413 SELECT
2414 ra.id, ra.roleid, ra.userid
2415 FROM
2416 {$CFG->prefix}role_assignments ra,
2417 {$CFG->prefix}context con,
2418 {$CFG->prefix}course_meta cm
2419 WHERE
2420 ra.contextid = con.id AND
2421 con.contextlevel = " . CONTEXT_COURSE . " AND
2422 con.instanceid = cm.child_course AND
2423 cm.parent_course = {$course->id} AND
2424 $roleexclusions
2425 NOT EXISTS (
2426 SELECT 1 FROM
2427 {$CFG->prefix}role_assignments ra2
2428 WHERE
2429 ra2.userid = ra.userid AND
2430 ra2.roleid = ra.roleid AND
2431 ra2.contextid = {$context->id}
2433 ")) {
2434 $assignments = array();
2437 // Get assignments of a user to a role that exist in the meta course, but
2438 // not in any child courses. That is, get a list of the unassignments that need to be made.
2439 if (!$unassignments = get_records_sql("
2440 SELECT
2441 ra.id, ra.roleid, ra.userid
2442 FROM
2443 {$CFG->prefix}role_assignments ra
2444 WHERE
2445 ra.contextid = {$context->id} AND
2446 $roleexclusions
2447 NOT EXISTS (
2448 SELECT 1 FROM
2449 {$CFG->prefix}role_assignments ra2,
2450 {$CFG->prefix}context con2,
2451 {$CFG->prefix}course_meta cm
2452 WHERE
2453 ra2.userid = ra.userid AND
2454 ra2.roleid = ra.roleid AND
2455 ra2.contextid = con2.id AND
2456 con2.contextlevel = " . CONTEXT_COURSE . " AND
2457 con2.instanceid = cm.child_course AND
2458 cm.parent_course = {$course->id}
2460 ")) {
2461 $unassignments = array();
2464 $success = true;
2466 // Make the unassignments, if they are not managers.
2467 foreach ($unassignments as $unassignment) {
2468 if (!in_array($unassignment->userid, $managers)) {
2469 $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2473 // Make the assignments.
2474 foreach ($assignments as $assignment) {
2475 $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id) && $success;
2478 return $success;
2480 // TODO: finish timeend and timestart
2481 // maybe we could rely on cron job to do the cleaning from time to time
2485 * Adds a record to the metacourse table and calls sync_metacoures
2487 function add_to_metacourse ($metacourseid, $courseid) {
2489 if (!$metacourse = get_record("course","id",$metacourseid)) {
2490 return false;
2493 if (!$course = get_record("course","id",$courseid)) {
2494 return false;
2497 if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2498 $rec = new object();
2499 $rec->parent_course = $metacourseid;
2500 $rec->child_course = $courseid;
2501 if (!insert_record('course_meta',$rec)) {
2502 return false;
2504 return sync_metacourse($metacourseid);
2506 return true;
2511 * Removes the record from the metacourse table and calls sync_metacourse
2513 function remove_from_metacourse($metacourseid, $courseid) {
2515 if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2516 return sync_metacourse($metacourseid);
2518 return false;
2523 * Determines if a user is currently logged in
2525 * @uses $USER
2526 * @return bool
2528 function isloggedin() {
2529 global $USER;
2531 return (!empty($USER->id));
2535 * Determines if a user is logged in as real guest user with username 'guest'.
2536 * This function is similar to original isguest() in 1.6 and earlier.
2537 * Current isguest() is deprecated - do not use it anymore.
2539 * @param $user mixed user object or id, $USER if not specified
2540 * @return bool true if user is the real guest user, false if not logged in or other user
2542 function isguestuser($user=NULL) {
2543 global $USER;
2544 if ($user === NULL) {
2545 $user = $USER;
2546 } else if (is_numeric($user)) {
2547 $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2550 if (empty($user->id)) {
2551 return false; // not logged in, can not be guest
2554 return ($user->username == 'guest');
2558 * Determines if the currently logged in user is in editing mode.
2559 * Note: originally this function had $userid parameter - it was not usable anyway
2561 * @uses $USER, $PAGE
2562 * @return bool
2564 function isediting() {
2565 global $USER, $PAGE;
2567 if (empty($USER->editing)) {
2568 return false;
2569 } elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
2570 return $PAGE->user_allowed_editing();
2572 return true;//false;
2576 * Determines if the logged in user is currently moving an activity
2578 * @uses $USER
2579 * @param int $courseid The id of the course being tested
2580 * @return bool
2582 function ismoving($courseid) {
2583 global $USER;
2585 if (!empty($USER->activitycopy)) {
2586 return ($USER->activitycopycourse == $courseid);
2588 return false;
2592 * Given an object containing firstname and lastname
2593 * values, this function returns a string with the
2594 * full name of the person.
2595 * The result may depend on system settings
2596 * or language. 'override' will force both names
2597 * to be used even if system settings specify one.
2599 * @uses $CFG
2600 * @uses $SESSION
2601 * @param object $user A {@link $USER} object to get full name of
2602 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2604 function fullname($user, $override=false) {
2606 global $CFG, $SESSION;
2608 if (!isset($user->firstname) and !isset($user->lastname)) {
2609 return '';
2612 if (!$override) {
2613 if (!empty($CFG->forcefirstname)) {
2614 $user->firstname = $CFG->forcefirstname;
2616 if (!empty($CFG->forcelastname)) {
2617 $user->lastname = $CFG->forcelastname;
2621 if (!empty($SESSION->fullnamedisplay)) {
2622 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2625 if ($CFG->fullnamedisplay == 'firstname lastname') {
2626 return $user->firstname .' '. $user->lastname;
2628 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2629 return $user->lastname .' '. $user->firstname;
2631 } else if ($CFG->fullnamedisplay == 'firstname') {
2632 if ($override) {
2633 return get_string('fullnamedisplay', '', $user);
2634 } else {
2635 return $user->firstname;
2639 return get_string('fullnamedisplay', '', $user);
2643 * Sets a moodle cookie with an encrypted string
2645 * @uses $CFG
2646 * @uses DAYSECS
2647 * @uses HOURSECS
2648 * @param string $thing The string to encrypt and place in a cookie
2650 function set_moodle_cookie($thing) {
2651 global $CFG;
2653 if ($thing == 'guest') { // Ignore guest account
2654 return;
2657 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2659 $days = 60;
2660 $seconds = DAYSECS*$days;
2662 // no need to set secure or http cookie only here - it is not secret
2663 setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath);
2664 setCookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath);
2668 * Gets a moodle cookie with an encrypted string
2670 * @uses $CFG
2671 * @return string
2673 function get_moodle_cookie() {
2674 global $CFG;
2676 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2678 if (empty($_COOKIE[$cookiename])) {
2679 return '';
2680 } else {
2681 $thing = rc4decrypt($_COOKIE[$cookiename]);
2682 return ($thing == 'guest') ? '': $thing; // Ignore guest account
2687 * Returns whether a given authentication plugin exists.
2689 * @uses $CFG
2690 * @param string $auth Form of authentication to check for. Defaults to the
2691 * global setting in {@link $CFG}.
2692 * @return boolean Whether the plugin is available.
2694 function exists_auth_plugin($auth) {
2695 global $CFG;
2697 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2698 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2700 return false;
2704 * Checks if a given plugin is in the list of enabled authentication plugins.
2706 * @param string $auth Authentication plugin.
2707 * @return boolean Whether the plugin is enabled.
2709 function is_enabled_auth($auth) {
2710 if (empty($auth)) {
2711 return false;
2714 $enabled = get_enabled_auth_plugins();
2716 return in_array($auth, $enabled);
2720 * Returns an authentication plugin instance.
2722 * @uses $CFG
2723 * @param string $auth name of authentication plugin
2724 * @return object An instance of the required authentication plugin.
2726 function get_auth_plugin($auth) {
2727 global $CFG;
2729 // check the plugin exists first
2730 if (! exists_auth_plugin($auth)) {
2731 error("Authentication plugin '$auth' not found.");
2734 // return auth plugin instance
2735 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2736 $class = "auth_plugin_$auth";
2737 return new $class;
2741 * Returns array of active auth plugins.
2743 * @param bool $fix fix $CFG->auth if needed
2744 * @return array
2746 function get_enabled_auth_plugins($fix=false) {
2747 global $CFG;
2749 $default = array('manual', 'nologin');
2751 if (empty($CFG->auth)) {
2752 $auths = array();
2753 } else {
2754 $auths = explode(',', $CFG->auth);
2757 if ($fix) {
2758 $auths = array_unique($auths);
2759 foreach($auths as $k=>$authname) {
2760 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
2761 unset($auths[$k]);
2764 $newconfig = implode(',', $auths);
2765 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
2766 set_config('auth', $newconfig);
2770 return (array_merge($default, $auths));
2774 * Returns true if an internal authentication method is being used.
2775 * if method not specified then, global default is assumed
2777 * @uses $CFG
2778 * @param string $auth Form of authentication required
2779 * @return bool
2781 function is_internal_auth($auth) {
2782 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2783 return $authplugin->is_internal();
2787 * Returns an array of user fields
2789 * @uses $CFG
2790 * @uses $db
2791 * @return array User field/column names
2793 function get_user_fieldnames() {
2795 global $CFG, $db;
2797 $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2798 unset($fieldarray['ID']);
2800 return $fieldarray;
2804 * Creates the default "guest" user. Used both from
2805 * admin/index.php and login/index.php
2806 * @return mixed user object created or boolean false if the creation has failed
2808 function create_guest_record() {
2810 global $CFG;
2812 $guest = new stdClass();
2813 $guest->auth = 'manual';
2814 $guest->username = 'guest';
2815 $guest->password = hash_internal_user_password('guest');
2816 $guest->firstname = addslashes(get_string('guestuser'));
2817 $guest->lastname = ' ';
2818 $guest->email = 'root@localhost';
2819 $guest->description = addslashes(get_string('guestuserinfo'));
2820 $guest->mnethostid = $CFG->mnet_localhost_id;
2821 $guest->confirmed = 1;
2822 $guest->lang = $CFG->lang;
2823 $guest->timemodified= time();
2825 if (! $guest->id = insert_record("user", $guest)) {
2826 return false;
2829 return $guest;
2833 * Creates a bare-bones user record
2835 * @uses $CFG
2836 * @param string $username New user's username to add to record
2837 * @param string $password New user's password to add to record
2838 * @param string $auth Form of authentication required
2839 * @return object A {@link $USER} object
2840 * @todo Outline auth types and provide code example
2842 function create_user_record($username, $password, $auth='manual') {
2843 global $CFG;
2845 //just in case check text case
2846 $username = trim(moodle_strtolower($username));
2848 $authplugin = get_auth_plugin($auth);
2850 if ($newinfo = $authplugin->get_userinfo($username)) {
2851 $newinfo = truncate_userinfo($newinfo);
2852 foreach ($newinfo as $key => $value){
2853 $newuser->$key = addslashes($value);
2857 if (!empty($newuser->email)) {
2858 if (email_is_not_allowed($newuser->email)) {
2859 unset($newuser->email);
2863 $newuser->auth = $auth;
2864 $newuser->username = $username;
2866 // fix for MDL-8480
2867 // user CFG lang for user if $newuser->lang is empty
2868 // or $user->lang is not an installed language
2869 $sitelangs = array_keys(get_list_of_languages());
2870 if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
2871 $newuser -> lang = $CFG->lang;
2873 $newuser->confirmed = 1;
2874 $newuser->lastip = getremoteaddr();
2875 $newuser->timemodified = time();
2876 $newuser->mnethostid = $CFG->mnet_localhost_id;
2878 if (insert_record('user', $newuser)) {
2879 $user = get_complete_user_data('username', $newuser->username);
2880 if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
2881 set_user_preference('auth_forcepasswordchange', 1, $user->id);
2883 update_internal_user_password($user, $password);
2884 return $user;
2886 return false;
2890 * Will update a local user record from an external source
2892 * @uses $CFG
2893 * @param string $username New user's username to add to record
2894 * @return user A {@link $USER} object
2896 function update_user_record($username, $authplugin) {
2897 $username = trim(moodle_strtolower($username)); /// just in case check text case
2899 $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2900 $userauth = get_auth_plugin($oldinfo->auth);
2902 if ($newinfo = $userauth->get_userinfo($username)) {
2903 $newinfo = truncate_userinfo($newinfo);
2904 foreach ($newinfo as $key => $value){
2905 if ($key === 'username') {
2906 // 'username' is not a mapped updateable/lockable field, so skip it.
2907 continue;
2909 $confval = $userauth->config->{'field_updatelocal_' . $key};
2910 $lockval = $userauth->config->{'field_lock_' . $key};
2911 if (empty($confval) || empty($lockval)) {
2912 continue;
2914 if ($confval === 'onlogin') {
2915 $value = addslashes(stripslashes($value)); // Just in case
2916 // MDL-4207 Don't overwrite modified user profile values with
2917 // empty LDAP values when 'unlocked if empty' is set. The purpose
2918 // of the setting 'unlocked if empty' is to allow the user to fill
2919 // in a value for the selected field _if LDAP is giving
2920 // nothing_ for this field. Thus it makes sense to let this value
2921 // stand in until LDAP is giving a value for this field.
2922 if (!(empty($value) && $lockval === 'unlockedifempty')) {
2923 set_field('user', $key, $value, 'username', $username)
2924 || error_log("Error updating $key for $username");
2930 return get_complete_user_data('username', $username);
2933 function truncate_userinfo($info) {
2934 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2935 /// which may have large fields
2937 // define the limits
2938 $limit = array(
2939 'username' => 100,
2940 'idnumber' => 255,
2941 'firstname' => 100,
2942 'lastname' => 100,
2943 'email' => 100,
2944 'icq' => 15,
2945 'phone1' => 20,
2946 'phone2' => 20,
2947 'institution' => 40,
2948 'department' => 30,
2949 'address' => 70,
2950 'city' => 20,
2951 'country' => 2,
2952 'url' => 255,
2955 // apply where needed
2956 foreach (array_keys($info) as $key) {
2957 if (!empty($limit[$key])) {
2958 $info[$key] = trim(substr($info[$key],0, $limit[$key]));
2962 return $info;
2966 * Marks user deleted in internal user database and notifies the auth plugin.
2967 * Also unenrols user from all roles and does other cleanup.
2968 * @param object $user Userobject before delete (without system magic quotes)
2969 * @return boolean success
2971 function delete_user($user) {
2972 global $CFG;
2973 require_once($CFG->libdir.'/grouplib.php');
2974 require_once($CFG->libdir.'/gradelib.php');
2976 begin_sql();
2978 // delete all grades - backup is kept in grade_grades_history table
2979 if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
2980 foreach ($grades as $grade) {
2981 $grade->delete('userdelete');
2985 // remove from all groups
2986 delete_records('groups_members', 'userid', $user->id);
2988 // unenrol from all roles in all contexts
2989 role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
2991 // now do a final accesslib cleanup - removes all role assingments in user context and context itself
2992 delete_context(CONTEXT_USER, $user->id);
2994 require_once($CFG->dirroot.'/tag/lib.php');
2995 tag_set('user', $user->id, array());
2997 // workaround for bulk deletes of users with the same email address
2998 $delname = addslashes("$user->email.".time());
2999 while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
3000 $delname++;
3003 // mark internal user record as "deleted"
3004 $updateuser = new object();
3005 $updateuser->id = $user->id;
3006 $updateuser->deleted = 1;
3007 $updateuser->username = $delname; // Remember it just in case
3008 $updateuser->email = ''; // Clear this field to free it up
3009 $updateuser->idnumber = ''; // Clear this field to free it up
3010 $updateuser->timemodified = time();
3012 if (update_record('user', $updateuser)) {
3013 commit_sql();
3014 // notify auth plugin - do not block the delete even when plugin fails
3015 $authplugin = get_auth_plugin($user->auth);
3016 $authplugin->user_delete($user);
3018 events_trigger('user_deleted', $user);
3019 return true;
3021 } else {
3022 rollback_sql();
3023 return false;
3028 * Retrieve the guest user object
3030 * @uses $CFG
3031 * @return user A {@link $USER} object
3033 function guest_user() {
3034 global $CFG;
3036 if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id)) {
3037 $newuser->confirmed = 1;
3038 $newuser->lang = $CFG->lang;
3039 $newuser->lastip = getremoteaddr();
3042 return $newuser;
3046 * Given a username and password, this function looks them
3047 * up using the currently selected authentication mechanism,
3048 * and if the authentication is successful, it returns a
3049 * valid $user object from the 'user' table.
3051 * Uses auth_ functions from the currently active auth module
3053 * After authenticate_user_login() returns success, you will need to
3054 * log that the user has logged in, and call complete_user_login() to set
3055 * the session up.
3057 * @uses $CFG
3058 * @param string $username User's username (with system magic quotes)
3059 * @param string $password User's password (with system magic quotes)
3060 * @return user|flase A {@link $USER} object or false if error
3062 function authenticate_user_login($username, $password) {
3064 global $CFG;
3066 $authsenabled = get_enabled_auth_plugins();
3068 if ($user = get_complete_user_data('username', $username)) {
3069 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
3070 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3071 add_to_log(0, 'login', 'error', 'index.php', $username);
3072 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3073 return false;
3075 if (!empty($user->deleted)) {
3076 add_to_log(0, 'login', 'error', 'index.php', $username);
3077 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3078 return false;
3080 $auths = array($auth);
3082 } else {
3083 $auths = $authsenabled;
3084 $user = new object();
3085 $user->id = 0; // User does not exist
3088 foreach ($auths as $auth) {
3089 $authplugin = get_auth_plugin($auth);
3091 // on auth fail fall through to the next plugin
3092 if (!$authplugin->user_login($username, $password)) {
3093 continue;
3096 // successful authentication
3097 if ($user->id) { // User already exists in database
3098 if (empty($user->auth)) { // For some reason auth isn't set yet
3099 set_field('user', 'auth', $auth, 'username', $username);
3100 $user->auth = $auth;
3103 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3105 if (!$authplugin->is_internal()) { // update user record from external DB
3106 $user = update_user_record($username, get_auth_plugin($user->auth));
3108 } else {
3109 // if user not found, create him
3110 $user = create_user_record($username, $password, $auth);
3113 $authplugin->sync_roles($user);
3115 foreach ($authsenabled as $hau) {
3116 $hauth = get_auth_plugin($hau);
3117 $hauth->user_authenticated_hook($user, $username, $password);
3120 /// Log in to a second system if necessary
3121 /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
3122 if (!empty($CFG->sso)) {
3123 include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
3124 if (function_exists('sso_user_login')) {
3125 if (!sso_user_login($username, $password)) { // Perform the signon process
3126 notify('Second sign-on failed');
3131 if ($user->id===0) {
3132 return false;
3134 return $user;
3137 // failed if all the plugins have failed
3138 add_to_log(0, 'login', 'error', 'index.php', $username);
3139 if (debugging('', DEBUG_ALL)) {
3140 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3142 return false;
3146 * Call to complete the user login process after authenticate_user_login()
3147 * has succeeded. It will setup the $USER variable and other required bits
3148 * and pieces.
3150 * NOTE:
3151 * - It will NOT log anything -- up to the caller to decide what to log.
3155 * @uses $CFG, $USER
3156 * @param string $user obj
3157 * @return user|flase A {@link $USER} object or false if error
3159 function complete_user_login($user) {
3160 global $CFG, $USER;
3162 $USER = $user; // this is required because we need to access preferences here!
3164 reload_user_preferences();
3166 update_user_login_times();
3167 if (empty($CFG->nolastloggedin)) {
3168 set_moodle_cookie($USER->username);
3169 } else {
3170 // do not store last logged in user in cookie
3171 // auth plugins can temporarily override this from loginpage_hook()
3172 // do not save $CFG->nolastloggedin in database!
3173 set_moodle_cookie('nobody');
3175 set_login_session_preferences();
3177 // Call enrolment plugins
3178 check_enrolment_plugins($user);
3180 /// This is what lets the user do anything on the site :-)
3181 load_all_capabilities();
3183 /// Select password change url
3184 $userauth = get_auth_plugin($USER->auth);
3186 /// check whether the user should be changing password
3187 if (get_user_preferences('auth_forcepasswordchange', false)){
3188 if ($userauth->can_change_password()) {
3189 if ($changeurl = $userauth->change_password_url()) {
3190 redirect($changeurl);
3191 } else {
3192 redirect($CFG->httpswwwroot.'/login/change_password.php');
3194 } else {
3195 print_error('nopasswordchangeforced', 'auth');
3198 return $USER;
3202 * Compare password against hash stored in internal user table.
3203 * If necessary it also updates the stored hash to new format.
3205 * @param object user
3206 * @param string plain text password
3207 * @return bool is password valid?
3209 function validate_internal_user_password(&$user, $password) {
3210 global $CFG;
3212 if (!isset($CFG->passwordsaltmain)) {
3213 $CFG->passwordsaltmain = '';
3216 $validated = false;
3218 // get password original encoding in case it was not updated to unicode yet
3219 $textlib = textlib_get_instance();
3220 $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
3222 if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
3223 or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
3224 $validated = true;
3225 } else {
3226 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
3227 $alt = 'passwordsaltalt'.$i;
3228 if (!empty($CFG->$alt)) {
3229 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
3230 $validated = true;
3231 break;
3237 if ($validated) {
3238 // force update of password hash using latest main password salt and encoding if needed
3239 update_internal_user_password($user, $password);
3242 return $validated;
3246 * Calculate hashed value from password using current hash mechanism.
3248 * @param string password
3249 * @return string password hash
3251 function hash_internal_user_password($password) {
3252 global $CFG;
3254 if (isset($CFG->passwordsaltmain)) {
3255 return md5($password.$CFG->passwordsaltmain);
3256 } else {
3257 return md5($password);
3262 * Update pssword hash in user object.
3264 * @param object user
3265 * @param string plain text password
3266 * @param bool store changes also in db, default true
3267 * @return true if hash changed
3269 function update_internal_user_password(&$user, $password) {
3270 global $CFG;
3272 $authplugin = get_auth_plugin($user->auth);
3273 if (!empty($authplugin->config->preventpassindb)) {
3274 $hashedpassword = 'not cached';
3275 } else {
3276 $hashedpassword = hash_internal_user_password($password);
3279 return set_field('user', 'password', $hashedpassword, 'id', $user->id);
3283 * Get a complete user record, which includes all the info
3284 * in the user record
3285 * Intended for setting as $USER session variable
3287 * @uses $CFG
3288 * @uses SITEID
3289 * @param string $field The user field to be checked for a given value.
3290 * @param string $value The value to match for $field.
3291 * @return user A {@link $USER} object.
3293 function get_complete_user_data($field, $value, $mnethostid=null) {
3295 global $CFG;
3297 if (!$field || !$value) {
3298 return false;
3301 /// Build the WHERE clause for an SQL query
3303 $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
3305 // If we are loading user data based on anything other than id,
3306 // we must also restrict our search based on mnet host.
3307 if ($field != 'id') {
3308 if (empty($mnethostid)) {
3309 // if empty, we restrict to local users
3310 $mnethostid = $CFG->mnet_localhost_id;
3313 if (!empty($mnethostid)) {
3314 $mnethostid = (int)$mnethostid;
3315 $constraints .= ' AND mnethostid = ' . $mnethostid;
3318 /// Get all the basic user data
3320 if (! $user = get_record_select('user', $constraints)) {
3321 return false;
3324 /// Get various settings and preferences
3326 if ($displays = get_records('course_display', 'userid', $user->id)) {
3327 foreach ($displays as $display) {
3328 $user->display[$display->course] = $display->display;
3332 $user->preference = get_user_preferences(null, null, $user->id);
3334 $user->lastcourseaccess = array(); // during last session
3335 $user->currentcourseaccess = array(); // during current session
3336 if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
3337 foreach ($lastaccesses as $lastaccess) {
3338 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
3342 $sql = "SELECT g.id, g.courseid
3343 FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
3344 WHERE gm.groupid=g.id AND gm.userid={$user->id}";
3346 // this is a special hack to speedup calendar display
3347 $user->groupmember = array();
3348 if ($groups = get_records_sql($sql)) {
3349 foreach ($groups as $group) {
3350 if (!array_key_exists($group->courseid, $user->groupmember)) {
3351 $user->groupmember[$group->courseid] = array();
3353 $user->groupmember[$group->courseid][$group->id] = $group->id;
3357 /// Add the custom profile fields to the user record
3358 include_once($CFG->dirroot.'/user/profile/lib.php');
3359 $customfields = (array)profile_user_record($user->id);
3360 foreach ($customfields as $cname=>$cvalue) {
3361 if (!isset($user->$cname)) { // Don't overwrite any standard fields
3362 $user->$cname = $cvalue;
3366 /// Rewrite some variables if necessary
3367 if (!empty($user->description)) {
3368 $user->description = true; // No need to cart all of it around
3370 if ($user->username == 'guest') {
3371 $user->lang = $CFG->lang; // Guest language always same as site
3372 $user->firstname = get_string('guestuser'); // Name always in current language
3373 $user->lastname = ' ';
3376 $user->sesskey = random_string(10);
3377 $user->sessionIP = md5(getremoteaddr()); // Store the current IP in the session
3379 return $user;
3383 * @uses $CFG
3384 * @param string $password the password to be checked agains the password policy
3385 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3386 * @return bool true if the password is valid according to the policy. false otherwise.
3388 function check_password_policy($password, &$errmsg) {
3389 global $CFG;
3391 if (empty($CFG->passwordpolicy)) {
3392 return true;
3395 $textlib = textlib_get_instance();
3396 $errmsg = '';
3397 if ($textlib->strlen($password) < $CFG->minpasswordlength) {
3398 $errmsg = get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength);
3400 } else if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
3401 $errmsg = get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits);
3403 } else if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
3404 $errmsg = get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower);
3406 } else if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
3407 $errmsg = get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper);
3409 } else if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
3410 $errmsg = get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum);
3412 } else if ($password == 'admin' or $password == 'password') {
3413 $errmsg = get_string('unsafepassword');
3416 if ($errmsg == '') {
3417 return true;
3418 } else {
3419 return false;
3425 * When logging in, this function is run to set certain preferences
3426 * for the current SESSION
3428 function set_login_session_preferences() {
3429 global $SESSION, $CFG;
3431 $SESSION->justloggedin = true;
3433 unset($SESSION->lang);
3435 // Restore the calendar filters, if saved
3436 if (intval(get_user_preferences('calendar_persistflt', 0))) {
3437 include_once($CFG->dirroot.'/calendar/lib.php');
3438 calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
3444 * Delete a course, including all related data from the database,
3445 * and any associated files from the moodledata folder.
3447 * @param mixed $courseorid The id of the course or course object to delete.
3448 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3449 * @return bool true if all the removals succeeded. false if there were any failures. If this
3450 * method returns false, some of the removals will probably have succeeded, and others
3451 * failed, but you have no way of knowing which.
3453 function delete_course($courseorid, $showfeedback = true) {
3454 global $CFG;
3455 $result = true;
3457 if (is_object($courseorid)) {
3458 $courseid = $courseorid->id;
3459 $course = $courseorid;
3460 } else {
3461 $courseid = $courseorid;
3462 if (!$course = get_record('course', 'id', $courseid)) {
3463 return false;
3467 // frontpage course can not be deleted!!
3468 if ($courseid == SITEID) {
3469 return false;
3472 if (!remove_course_contents($courseid, $showfeedback)) {
3473 if ($showfeedback) {
3474 notify("An error occurred while deleting some of the course contents.");
3476 $result = false;
3479 if (!delete_records("course", "id", $courseid)) {
3480 if ($showfeedback) {
3481 notify("An error occurred while deleting the main course record.");
3483 $result = false;
3486 /// Delete all roles and overiddes in the course context
3487 if (!delete_context(CONTEXT_COURSE, $courseid)) {
3488 if ($showfeedback) {
3489 notify("An error occurred while deleting the main course context.");
3491 $result = false;
3494 if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3495 if ($showfeedback) {
3496 notify("An error occurred while deleting the course files.");
3498 $result = false;
3501 if ($result) {
3502 //trigger events
3503 events_trigger('course_deleted', $course);
3506 return $result;
3510 * Clear a course out completely, deleting all content
3511 * but don't delete the course itself
3513 * @uses $CFG
3514 * @param int $courseid The id of the course that is being deleted
3515 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3516 * @return bool true if all the removals succeeded. false if there were any failures. If this
3517 * method returns false, some of the removals will probably have succeeded, and others
3518 * failed, but you have no way of knowing which.
3520 function remove_course_contents($courseid, $showfeedback=true) {
3522 global $CFG;
3523 require_once($CFG->libdir.'/questionlib.php');
3524 require_once($CFG->libdir.'/gradelib.php');
3526 $result = true;
3528 if (! $course = get_record('course', 'id', $courseid)) {
3529 error('Course ID was incorrect (can\'t find it)');
3532 $strdeleted = get_string('deleted');
3534 /// First delete every instance of every module
3536 if ($allmods = get_records('modules') ) {
3537 foreach ($allmods as $mod) {
3538 $modname = $mod->name;
3539 $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3540 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
3541 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
3542 $count=0;
3543 if (file_exists($modfile)) {
3544 include_once($modfile);
3545 if (function_exists($moddelete)) {
3546 if ($instances = get_records($modname, 'course', $course->id)) {
3547 foreach ($instances as $instance) {
3548 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
3549 /// Delete activity context questions and question categories
3550 question_delete_activity($cm, $showfeedback);
3552 if ($moddelete($instance->id)) {
3553 $count++;
3555 } else {
3556 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
3557 $result = false;
3559 if ($cm) {
3560 // delete cm and its context in correct order
3561 delete_records('course_modules', 'id', $cm->id);
3562 delete_context(CONTEXT_MODULE, $cm->id);
3566 } else {
3567 notify('Function '.$moddelete.'() doesn\'t exist!');
3568 $result = false;
3571 if (function_exists($moddeletecourse)) {
3572 $moddeletecourse($course, $showfeedback);
3575 if ($showfeedback) {
3576 notify($strdeleted .' '. $count .' x '. $modname);
3579 } else {
3580 error('No modules are installed!');
3583 /// Give local code a chance to delete its references to this course.
3584 require_once('locallib.php');
3585 notify_local_delete_course($courseid, $showfeedback);
3587 /// Delete course blocks
3589 if ($blocks = get_records_sql("SELECT *
3590 FROM {$CFG->prefix}block_instance
3591 WHERE pagetype = '".PAGE_COURSE_VIEW."'
3592 AND pageid = $course->id")) {
3593 if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3594 if ($showfeedback) {
3595 notify($strdeleted .' block_instance');
3598 require_once($CFG->libdir.'/blocklib.php');
3599 foreach ($blocks as $block) { /// Delete any associated contexts for this block
3601 delete_context(CONTEXT_BLOCK, $block->id);
3603 // fix for MDL-7164
3604 // Get the block object and call instance_delete()
3605 if (!$record = blocks_get_record($block->blockid)) {
3606 $result = false;
3607 continue;
3609 if (!$obj = block_instance($record->name, $block)) {
3610 $result = false;
3611 continue;
3613 // Return value ignored, in core mods this does not do anything, but just in case
3614 // third party blocks might have stuff to clean up
3615 // we execute this anyway
3616 $obj->instance_delete();
3619 } else {
3620 $result = false;
3624 /// Delete any groups, removing members and grouping/course links first.
3625 require_once($CFG->dirroot.'/group/lib.php');
3626 groups_delete_groupings($courseid, $showfeedback);
3627 groups_delete_groups($courseid, $showfeedback);
3629 /// Delete all related records in other tables that may have a courseid
3630 /// This array stores the tables that need to be cleared, as
3631 /// table_name => column_name that contains the course id.
3633 $tablestoclear = array(
3634 'event' => 'courseid', // Delete events
3635 'log' => 'course', // Delete logs
3636 'course_sections' => 'course', // Delete any course stuff
3637 'course_modules' => 'course',
3638 'backup_courses' => 'courseid', // Delete scheduled backup stuff
3639 'user_lastaccess' => 'courseid',
3640 'backup_log' => 'courseid'
3642 foreach ($tablestoclear as $table => $col) {
3643 if (delete_records($table, $col, $course->id)) {
3644 if ($showfeedback) {
3645 notify($strdeleted . ' ' . $table);
3647 } else {
3648 $result = false;
3653 /// Clean up metacourse stuff
3655 if ($course->metacourse) {
3656 delete_records("course_meta","parent_course",$course->id);
3657 sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3658 if ($showfeedback) {
3659 notify("$strdeleted course_meta");
3661 } else {
3662 if ($parents = get_records("course_meta","child_course",$course->id)) {
3663 foreach ($parents as $parent) {
3664 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3666 if ($showfeedback) {
3667 notify("$strdeleted course_meta");
3672 /// Delete questions and question categories
3673 question_delete_course($course, $showfeedback);
3675 /// Remove all data from gradebook
3676 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3677 remove_course_grades($courseid, $showfeedback);
3678 remove_grade_letters($context, $showfeedback);
3680 return $result;
3684 * Change dates in module - used from course reset.
3685 * @param strin $modname forum, assignent, etc
3686 * @param array $fields array of date fields from mod table
3687 * @param int $timeshift time difference
3688 * @return success
3690 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
3691 global $CFG;
3692 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
3694 $return = true;
3695 foreach ($fields as $field) {
3696 $updatesql = "UPDATE {$CFG->prefix}$modname
3697 SET $field = $field + ($timeshift)
3698 WHERE course=$courseid AND $field<>0 AND $field<>0";
3699 $return = execute_sql($updatesql, false) && $return;
3702 $refreshfunction = $modname.'_refresh_events';
3703 if (function_exists($refreshfunction)) {
3704 $refreshfunction($courseid);
3707 return $return;
3711 * This function will empty a course of user data.
3712 * It will retain the activities and the structure of the course.
3713 * @param object $data an object containing all the settings including courseid (without magic quotes)
3714 * @return array status array of array component, item, error
3716 function reset_course_userdata($data) {
3717 global $CFG, $USER;
3718 require_once($CFG->libdir.'/gradelib.php');
3719 require_once($CFG->dirroot.'/group/lib.php');
3721 $data->courseid = $data->id;
3722 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3724 // calculate the time shift of dates
3725 if (!empty($data->reset_start_date)) {
3726 // time part of course startdate should be zero
3727 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
3728 } else {
3729 $data->timeshift = 0;
3732 // result array: component, item, error
3733 $status = array();
3735 // start the resetting
3736 $componentstr = get_string('general');
3738 // move the course start time
3739 if (!empty($data->reset_start_date) and $data->timeshift) {
3740 // change course start data
3741 set_field('course', 'startdate', $data->reset_start_date, 'id', $data->courseid);
3742 // update all course and group events - do not move activity events
3743 $updatesql = "UPDATE {$CFG->prefix}event
3744 SET timestart = timestart + ({$data->timeshift})
3745 WHERE courseid={$data->courseid} AND instance=0";
3746 execute_sql($updatesql, false);
3748 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3751 if (!empty($data->reset_logs)) {
3752 delete_records('log', 'course', $data->courseid);
3753 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
3756 if (!empty($data->reset_events)) {
3757 delete_records('event', 'courseid', $data->courseid);
3758 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
3761 if (!empty($data->reset_notes)) {
3762 require_once($CFG->dirroot.'/notes/lib.php');
3763 note_delete_all($data->courseid);
3764 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
3767 $componentstr = get_string('roles');
3769 if (!empty($data->reset_roles_overrides)) {
3770 $children = get_child_contexts($context);
3771 foreach ($children as $child) {
3772 delete_records('role_capabilities', 'contextid', $child->id);
3774 delete_records('role_capabilities', 'contextid', $context->id);
3775 //force refresh for logged in users
3776 mark_context_dirty($context->path);
3777 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
3780 if (!empty($data->reset_roles_local)) {
3781 $children = get_child_contexts($context);
3782 foreach ($children as $child) {
3783 role_unassign(0, 0, 0, $child->id);
3785 //force refresh for logged in users
3786 mark_context_dirty($context->path);
3787 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
3790 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
3791 $data->unenrolled = array();
3792 if (!empty($data->reset_roles)) {
3793 foreach($data->reset_roles as $roleid) {
3794 if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) {
3795 foreach ($users as $user) {
3796 role_unassign($roleid, $user->id, 0, $context->id);
3797 if (!has_capability('moodle/course:view', $context, $user->id)) {
3798 $data->unenrolled[$user->id] = $user->id;
3804 if (!empty($data->unenrolled)) {
3805 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol').' ('.count($data->unenrolled).')', 'error'=>false);
3809 $componentstr = get_string('groups');
3811 // remove all group members
3812 if (!empty($data->reset_groups_members)) {
3813 groups_delete_group_members($data->courseid);
3814 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
3817 // remove all groups
3818 if (!empty($data->reset_groups_remove)) {
3819 groups_delete_groups($data->courseid, false);
3820 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
3823 // remove all grouping members
3824 if (!empty($data->reset_groupings_members)) {
3825 groups_delete_groupings_groups($data->courseid, false);
3826 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
3829 // remove all groupings
3830 if (!empty($data->reset_groupings_remove)) {
3831 groups_delete_groupings($data->courseid, false);
3832 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
3835 // Look in every instance of every module for data to delete
3836 $unsupported_mods = array();
3837 if ($allmods = get_records('modules') ) {
3838 foreach ($allmods as $mod) {
3839 $modname = $mod->name;
3840 if (!count_records($modname, 'course', $data->courseid)) {
3841 continue; // skip mods with no instances
3843 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
3844 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
3845 if (file_exists($modfile)) {
3846 include_once($modfile);
3847 if (function_exists($moddeleteuserdata)) {
3848 $modstatus = $moddeleteuserdata($data);
3849 if (is_array($modstatus)) {
3850 $status = array_merge($status, $modstatus);
3851 } else {
3852 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
3854 } else {
3855 $unsupported_mods[] = $mod;
3857 } else {
3858 debugging('Missing lib.php in '.$modname.' module!');
3863 // mention unsupported mods
3864 if (!empty($unsupported_mods)) {
3865 foreach($unsupported_mods as $mod) {
3866 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
3871 $componentstr = get_string('gradebook', 'grades');
3872 // reset gradebook
3873 if (!empty($data->reset_gradebook_items)) {
3874 remove_course_grades($data->courseid, false);
3875 grade_grab_course_grades($data->courseid);
3876 grade_regrade_final_grades($data->courseid);
3877 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
3879 } else if (!empty($data->reset_gradebook_grades)) {
3880 grade_course_reset($data->courseid);
3881 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
3884 return $status;
3887 function generate_email_processing_address($modid,$modargs) {
3888 global $CFG;
3890 if (empty($CFG->siteidentifier)) { // Unique site identification code
3891 set_config('siteidentifier', random_string(32));
3894 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3895 return $header . substr(md5($header.$CFG->siteidentifier),0,16).'@'.$CFG->maildomain;
3899 function moodle_process_email($modargs,$body) {
3900 // the first char should be an unencoded letter. We'll take this as an action
3901 switch ($modargs{0}) {
3902 case 'B': { // bounce
3903 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3904 if ($user = get_record_select("user","id=$userid","id,email")) {
3905 // check the half md5 of their email
3906 $md5check = substr(md5($user->email),0,16);
3907 if ($md5check == substr($modargs, -16)) {
3908 set_bounce_count($user);
3910 // else maybe they've already changed it?
3913 break;
3914 // maybe more later?
3918 /// CORRESPONDENCE ////////////////////////////////////////////////
3921 * Get mailer instance, enable buffering, flush buffer or disable buffering.
3922 * @param $action string 'get', 'buffer', 'close' or 'flush'
3923 * @return reference to mailer instance if 'get' used or nothing
3925 function &get_mailer($action='get') {
3926 global $CFG;
3928 static $mailer = null;
3929 static $counter = 0;
3931 if (!isset($CFG->smtpmaxbulk)) {
3932 $CFG->smtpmaxbulk = 1;
3935 if ($action == 'get') {
3936 $prevkeepalive = false;
3938 if (isset($mailer) and $mailer->Mailer == 'smtp') {
3939 if ($counter < $CFG->smtpmaxbulk and empty($mailer->error_count)) {
3940 $counter++;
3941 // reset the mailer
3942 $mailer->Priority = 3;
3943 $mailer->CharSet = 'UTF-8'; // our default
3944 $mailer->ContentType = "text/plain";
3945 $mailer->Encoding = "8bit";
3946 $mailer->From = "root@localhost";
3947 $mailer->FromName = "Root User";
3948 $mailer->Sender = "";
3949 $mailer->Subject = "";
3950 $mailer->Body = "";
3951 $mailer->AltBody = "";
3952 $mailer->ConfirmReadingTo = "";
3954 $mailer->ClearAllRecipients();
3955 $mailer->ClearReplyTos();
3956 $mailer->ClearAttachments();
3957 $mailer->ClearCustomHeaders();
3958 return $mailer;
3961 $prevkeepalive = $mailer->SMTPKeepAlive;
3962 get_mailer('flush');
3965 include_once($CFG->libdir.'/phpmailer/class.phpmailer.php');
3966 $mailer = new phpmailer();
3968 $counter = 1;
3970 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
3971 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
3972 $mailer->CharSet = 'UTF-8';
3974 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
3975 // hmm, this is a bit hacky because LE should be private
3976 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
3977 $mailer->LE = "\r\n";
3978 } else {
3979 $mailer->LE = "\n";
3982 if ($CFG->smtphosts == 'qmail') {
3983 $mailer->IsQmail(); // use Qmail system
3985 } else if (empty($CFG->smtphosts)) {
3986 $mailer->IsMail(); // use PHP mail() = sendmail
3988 } else {
3989 $mailer->IsSMTP(); // use SMTP directly
3990 if (!empty($CFG->debugsmtp)) {
3991 $mailer->SMTPDebug = true;
3993 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
3994 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
3996 if ($CFG->smtpuser) { // Use SMTP authentication
3997 $mailer->SMTPAuth = true;
3998 $mailer->Username = $CFG->smtpuser;
3999 $mailer->Password = $CFG->smtppass;
4003 return $mailer;
4006 $nothing = null;
4008 // keep smtp session open after sending
4009 if ($action == 'buffer') {
4010 if (!empty($CFG->smtpmaxbulk)) {
4011 get_mailer('flush');
4012 $m =& get_mailer();
4013 if ($m->Mailer == 'smtp') {
4014 $m->SMTPKeepAlive = true;
4017 return $nothing;
4020 // close smtp session, but continue buffering
4021 if ($action == 'flush') {
4022 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4023 if (!empty($mailer->SMTPDebug)) {
4024 echo '<pre>'."\n";
4026 $mailer->SmtpClose();
4027 if (!empty($mailer->SMTPDebug)) {
4028 echo '</pre>';
4031 return $nothing;
4034 // close smtp session, do not buffer anymore
4035 if ($action == 'close') {
4036 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4037 get_mailer('flush');
4038 $mailer->SMTPKeepAlive = false;
4040 $mailer = null; // better force new instance
4041 return $nothing;
4046 * Send an email to a specified user
4048 * @uses $CFG
4049 * @uses $FULLME
4050 * @uses $MNETIDPJUMPURL IdentityProvider(IDP) URL user hits to jump to mnet peer.
4051 * @uses SITEID
4052 * @param user $user A {@link $USER} object
4053 * @param user $from A {@link $USER} object
4054 * @param string $subject plain text subject line of the email
4055 * @param string $messagetext plain text version of the message
4056 * @param string $messagehtml complete html version of the message (optional)
4057 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4058 * @param string $attachname the name of the file (extension indicates MIME)
4059 * @param bool $usetrueaddress determines whether $from email address should
4060 * be sent out. Will be overruled by user profile setting for maildisplay
4061 * @param int $wordwrapwidth custom word wrap width
4062 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4063 * was blocked by user and "false" if there was another sort of error.
4065 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4067 global $CFG, $FULLME, $MNETIDPJUMPURL;
4068 static $mnetjumps = array();
4070 if (empty($user)) {
4071 return false;
4074 if (!empty($CFG->noemailever)) {
4075 // hidden setting for development sites, set in config.php if needed
4076 return true;
4079 // skip mail to suspended users
4080 if (isset($user->auth) && $user->auth=='nologin') {
4081 return true;
4084 if (!empty($user->emailstop)) {
4085 return 'emailstop';
4088 if (over_bounce_threshold($user)) {
4089 error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
4090 return false;
4093 // If the user is a remote mnet user, parse the email text for URL to the
4094 // wwwroot and modify the url to direct the user's browser to login at their
4095 // home site (identity provider - idp) before hitting the link itself
4096 if ($user->mnethostid > 1) {
4097 require_once($CFG->dirroot.'/mnet/lib.php');
4098 // Form the request url to hit the idp's jump.php
4099 if (isset($mnetjumps[$user->mnethostid])) {
4100 $MNETIDPJUMPURL = $mnetjumps[$user->mnethostid];
4101 } else {
4102 $idp = mnet_get_peer_host($user->mnethostid);
4103 $idpjumppath = '/auth/mnet/jump.php';
4104 $MNETIDPJUMPURL = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
4105 $mnetjumps[$user->mnethostid] = $MNETIDPJUMPURL;
4108 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4109 'mnet_sso_apply_indirection',
4110 $messagetext);
4111 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4112 'mnet_sso_apply_indirection',
4113 $messagehtml);
4115 $mail =& get_mailer();
4117 if (!empty($mail->SMTPDebug)) {
4118 echo '<pre>' . "\n";
4121 /// We are going to use textlib services here
4122 $textlib = textlib_get_instance();
4124 $supportuser = generate_email_supportuser();
4126 // make up an email address for handling bounces
4127 if (!empty($CFG->handlebounces)) {
4128 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
4129 $mail->Sender = generate_email_processing_address(0,$modargs);
4130 } else {
4131 $mail->Sender = $supportuser->email;
4134 if (is_string($from)) { // So we can pass whatever we want if there is need
4135 $mail->From = $CFG->noreplyaddress;
4136 $mail->FromName = $from;
4137 } else if ($usetrueaddress and $from->maildisplay) {
4138 $mail->From = $from->email;
4139 $mail->FromName = fullname($from);
4140 } else {
4141 $mail->From = $CFG->noreplyaddress;
4142 $mail->FromName = fullname($from);
4143 if (empty($replyto)) {
4144 $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
4148 if (!empty($replyto)) {
4149 $mail->AddReplyTo($replyto,$replytoname);
4152 $mail->Subject = substr(stripslashes($subject), 0, 900);
4154 $mail->AddAddress($user->email, fullname($user) );
4156 $mail->WordWrap = $wordwrapwidth; // set word wrap
4158 if (!empty($from->customheaders)) { // Add custom headers
4159 if (is_array($from->customheaders)) {
4160 foreach ($from->customheaders as $customheader) {
4161 $mail->AddCustomHeader($customheader);
4163 } else {
4164 $mail->AddCustomHeader($from->customheaders);
4168 if (!empty($from->priority)) {
4169 $mail->Priority = $from->priority;
4172 if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
4173 $mail->IsHTML(true);
4174 $mail->Encoding = 'quoted-printable'; // Encoding to use
4175 $mail->Body = $messagehtml;
4176 $mail->AltBody = "\n$messagetext\n";
4177 } else {
4178 $mail->IsHTML(false);
4179 $mail->Body = "\n$messagetext\n";
4182 if ($attachment && $attachname) {
4183 if (ereg( "\\.\\." ,$attachment )) { // Security check for ".." in dir path
4184 $mail->AddAddress($supportuser->email, fullname($supportuser, true) );
4185 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4186 } else {
4187 require_once($CFG->libdir.'/filelib.php');
4188 $mimetype = mimeinfo('type', $attachname);
4189 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
4195 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
4196 /// encoding to the specified one
4197 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
4198 /// Set it to site mail charset
4199 $charset = $CFG->sitemailcharset;
4200 /// Overwrite it with the user mail charset
4201 if (!empty($CFG->allowusermailcharset)) {
4202 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
4203 $charset = $useremailcharset;
4206 /// If it has changed, convert all the necessary strings
4207 $charsets = get_list_of_charsets();
4208 unset($charsets['UTF-8']);
4209 if (in_array($charset, $charsets)) {
4210 /// Save the new mail charset
4211 $mail->CharSet = $charset;
4212 /// And convert some strings
4213 $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
4214 foreach ($mail->ReplyTo as $key => $rt) { //ReplyTo Names
4215 $mail->ReplyTo[$key][1] = $textlib->convert($rt, 'utf-8', $mail->CharSet);
4217 $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet); //Subject
4218 foreach ($mail->to as $key => $to) {
4219 $mail->to[$key][1] = $textlib->convert($to, 'utf-8', $mail->CharSet); //To Names
4221 $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet); //Body
4222 $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet); //Subject
4226 if ($mail->Send()) {
4227 set_send_count($user);
4228 $mail->IsSMTP(); // use SMTP directly
4229 if (!empty($mail->SMTPDebug)) {
4230 echo '</pre>';
4232 return true;
4233 } else {
4234 mtrace('ERROR: '. $mail->ErrorInfo);
4235 add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
4236 if (!empty($mail->SMTPDebug)) {
4237 echo '</pre>';
4239 return false;
4244 * Generate a signoff for emails based on support settings
4247 function generate_email_signoff() {
4248 global $CFG;
4250 $signoff = "\n";
4251 if (!empty($CFG->supportname)) {
4252 $signoff .= $CFG->supportname."\n";
4254 if (!empty($CFG->supportemail)) {
4255 $signoff .= $CFG->supportemail."\n";
4257 if (!empty($CFG->supportpage)) {
4258 $signoff .= $CFG->supportpage."\n";
4260 return $signoff;
4264 * Generate a fake user for emails based on support settings
4267 function generate_email_supportuser() {
4269 global $CFG;
4271 static $supportuser;
4273 if (!empty($supportuser)) {
4274 return $supportuser;
4277 $supportuser = new object;
4278 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
4279 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
4280 $supportuser->lastname = '';
4281 $supportuser->maildisplay = true;
4283 return $supportuser;
4288 * Sets specified user's password and send the new password to the user via email.
4290 * @uses $CFG
4291 * @param user $user A {@link $USER} object
4292 * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
4293 * was blocked by user and "false" if there was another sort of error.
4295 function setnew_password_and_mail($user) {
4297 global $CFG;
4299 $site = get_site();
4301 $supportuser = generate_email_supportuser();
4303 $newpassword = generate_password();
4305 if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
4306 trigger_error('Could not set user password!');
4307 return false;
4310 $a = new object();
4311 $a->firstname = fullname($user, true);
4312 $a->sitename = format_string($site->fullname);
4313 $a->username = $user->username;
4314 $a->newpassword = $newpassword;
4315 $a->link = $CFG->wwwroot .'/login/';
4316 $a->signoff = generate_email_signoff();
4318 $message = get_string('newusernewpasswordtext', '', $a);
4320 $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
4322 return email_to_user($user, $supportuser, $subject, $message);
4327 * Resets specified user's password and send the new password to the user via email.
4329 * @uses $CFG
4330 * @param user $user A {@link $USER} object
4331 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4332 * was blocked by user and "false" if there was another sort of error.
4334 function reset_password_and_mail($user) {
4336 global $CFG;
4338 $site = get_site();
4339 $supportuser = generate_email_supportuser();
4341 $userauth = get_auth_plugin($user->auth);
4342 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
4343 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4344 return false;
4347 $newpassword = generate_password();
4349 if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
4350 error("Could not set user password!");
4353 $a = new object();
4354 $a->firstname = $user->firstname;
4355 $a->sitename = format_string($site->fullname);
4356 $a->username = $user->username;
4357 $a->newpassword = $newpassword;
4358 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
4359 $a->signoff = generate_email_signoff();
4361 $message = get_string('newpasswordtext', '', $a);
4363 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
4365 return email_to_user($user, $supportuser, $subject, $message);
4370 * Send email to specified user with confirmation text and activation link.
4372 * @uses $CFG
4373 * @param user $user A {@link $USER} object
4374 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4375 * was blocked by user and "false" if there was another sort of error.
4377 function send_confirmation_email($user) {
4379 global $CFG;
4381 $site = get_site();
4382 $supportuser = generate_email_supportuser();
4384 $data = new object();
4385 $data->firstname = fullname($user);
4386 $data->sitename = format_string($site->fullname);
4387 $data->admin = generate_email_signoff();
4389 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
4391 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
4392 $message = get_string('emailconfirmation', '', $data);
4393 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4395 $user->mailformat = 1; // Always send HTML version as well
4397 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4402 * send_password_change_confirmation_email.
4404 * @uses $CFG
4405 * @param user $user A {@link $USER} object
4406 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4407 * was blocked by user and "false" if there was another sort of error.
4409 function send_password_change_confirmation_email($user) {
4411 global $CFG;
4413 $site = get_site();
4414 $supportuser = generate_email_supportuser();
4416 $data = new object();
4417 $data->firstname = $user->firstname;
4418 $data->sitename = format_string($site->fullname);
4419 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
4420 $data->admin = generate_email_signoff();
4422 $message = get_string('emailpasswordconfirmation', '', $data);
4423 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
4425 return email_to_user($user, $supportuser, $subject, $message);
4430 * send_password_change_info.
4432 * @uses $CFG
4433 * @param user $user A {@link $USER} object
4434 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4435 * was blocked by user and "false" if there was another sort of error.
4437 function send_password_change_info($user) {
4439 global $CFG;
4441 $site = get_site();
4442 $supportuser = generate_email_supportuser();
4443 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4445 $data = new object();
4446 $data->firstname = $user->firstname;
4447 $data->sitename = format_string($site->fullname);
4448 $data->admin = generate_email_signoff();
4450 $userauth = get_auth_plugin($user->auth);
4452 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
4453 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4454 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4455 return email_to_user($user, $supportuser, $subject, $message);
4458 if ($userauth->can_change_password() and $userauth->change_password_url()) {
4459 // we have some external url for password changing
4460 $data->link .= $userauth->change_password_url();
4462 } else {
4463 //no way to change password, sorry
4464 $data->link = '';
4467 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
4468 $message = get_string('emailpasswordchangeinfo', '', $data);
4469 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4470 } else {
4471 $message = get_string('emailpasswordchangeinfofail', '', $data);
4472 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4475 return email_to_user($user, $supportuser, $subject, $message);
4480 * Check that an email is allowed. It returns an error message if there
4481 * was a problem.
4483 * @uses $CFG
4484 * @param string $email Content of email
4485 * @return string|false
4487 function email_is_not_allowed($email) {
4489 global $CFG;
4491 if (!empty($CFG->allowemailaddresses)) {
4492 $allowed = explode(' ', $CFG->allowemailaddresses);
4493 foreach ($allowed as $allowedpattern) {
4494 $allowedpattern = trim($allowedpattern);
4495 if (!$allowedpattern) {
4496 continue;
4498 if (strpos($allowedpattern, '.') === 0) {
4499 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
4500 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4501 return false;
4504 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
4505 return false;
4508 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
4510 } else if (!empty($CFG->denyemailaddresses)) {
4511 $denied = explode(' ', $CFG->denyemailaddresses);
4512 foreach ($denied as $deniedpattern) {
4513 $deniedpattern = trim($deniedpattern);
4514 if (!$deniedpattern) {
4515 continue;
4517 if (strpos($deniedpattern, '.') === 0) {
4518 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
4519 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4520 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4523 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
4524 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4529 return false;
4532 function email_welcome_message_to_user($course, $user=NULL) {
4533 global $CFG, $USER;
4535 if (isset($CFG->sendcoursewelcomemessage) and !$CFG->sendcoursewelcomemessage) {
4536 return;
4539 if (empty($user)) {
4540 if (!isloggedin()) {
4541 return false;
4543 $user = $USER;
4546 if (!empty($course->welcomemessage)) {
4547 $message = $course->welcomemessage;
4548 } else {
4549 $a = new Object();
4550 $a->coursename = $course->fullname;
4551 $a->profileurl = "$CFG->wwwroot/user/view.php?id=$USER->id&course=$course->id";
4552 $message = get_string("welcometocoursetext", "", $a);
4555 /// If you don't want a welcome message sent, then make the message string blank.
4556 if (!empty($message)) {
4557 $subject = get_string('welcometocourse', '', format_string($course->fullname));
4559 if (! $teacher = get_teacher($course->id)) {
4560 $teacher = get_admin();
4562 email_to_user($user, $teacher, $subject, $message);
4566 /// FILE HANDLING /////////////////////////////////////////////
4570 * Makes an upload directory for a particular module.
4572 * @uses $CFG
4573 * @param int $courseid The id of the course in question - maps to id field of 'course' table.
4574 * @return string|false Returns full path to directory if successful, false if not
4576 function make_mod_upload_directory($courseid) {
4577 global $CFG;
4579 if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
4580 return false;
4583 $strreadme = get_string('readme');
4585 if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
4586 copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4587 } else {
4588 copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4590 return $moddata;
4594 * Makes a directory for a particular user.
4596 * @uses $CFG
4597 * @param int $userid The id of the user in question - maps to id field of 'user' table.
4598 * @param bool $test Whether we are only testing the return value (do not create the directory)
4599 * @return string|false Returns full path to directory if successful, false if not
4601 function make_user_directory($userid, $test=false) {
4602 global $CFG;
4604 if (is_bool($userid) || $userid < 0 || !ereg('^[0-9]{1,10}$', $userid) || $userid > 2147483647) {
4605 if (!$test) {
4606 notify("Given userid was not a valid integer! (" . gettype($userid) . " $userid)");
4608 return false;
4611 // Generate a two-level path for the userid. First level groups them by slices of 1000 users, second level is userid
4612 $level1 = floor($userid / 1000) * 1000;
4614 $userdir = "user/$level1/$userid";
4615 if ($test) {
4616 return $CFG->dataroot . '/' . $userdir;
4617 } else {
4618 return make_upload_directory($userdir);
4623 * Returns an array of full paths to user directories, indexed by their userids.
4625 * @param bool $only_non_empty Only return directories that contain files
4626 * @param bool $legacy Search for user directories in legacy location (dataroot/users/userid) instead of (dataroot/user/section/userid)
4627 * @return array An associative array: userid=>array(basedir => $basedir, userfolder => $userfolder)
4629 function get_user_directories($only_non_empty=true, $legacy=false) {
4630 global $CFG;
4632 $rootdir = $CFG->dataroot."/user";
4634 if ($legacy) {
4635 $rootdir = $CFG->dataroot."/users";
4637 $dirlist = array();
4639 //Check if directory exists
4640 if (check_dir_exists($rootdir, true)) {
4641 if ($legacy) {
4642 if ($userlist = get_directory_list($rootdir, '', true, true, false)) {
4643 foreach ($userlist as $userid) {
4644 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $userid);
4646 } else {
4647 notify("no directories found under $rootdir");
4649 } else {
4650 if ($grouplist =get_directory_list($rootdir, '', true, true, false)) { // directories will be in the form 0, 1000, 2000 etc...
4651 foreach ($grouplist as $group) {
4652 if ($userlist = get_directory_list("$rootdir/$group", '', true, true, false)) {
4653 foreach ($userlist as $userid) {
4654 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $group . '/' . $userid);
4660 } else {
4661 notify("$rootdir does not exist!");
4662 return false;
4664 return $dirlist;
4668 * Returns current name of file on disk if it exists.
4670 * @param string $newfile File to be verified
4671 * @return string Current name of file on disk if true
4673 function valid_uploaded_file($newfile) {
4674 if (empty($newfile)) {
4675 return '';
4677 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
4678 return $newfile['tmp_name'];
4679 } else {
4680 return '';
4685 * Returns the maximum size for uploading files.
4687 * There are seven possible upload limits:
4688 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
4689 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
4690 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
4691 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
4692 * 5. by the Moodle admin in $CFG->maxbytes
4693 * 6. by the teacher in the current course $course->maxbytes
4694 * 7. by the teacher for the current module, eg $assignment->maxbytes
4696 * These last two are passed to this function as arguments (in bytes).
4697 * Anything defined as 0 is ignored.
4698 * The smallest of all the non-zero numbers is returned.
4700 * @param int $sizebytes ?
4701 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4702 * @param int $modulebytes Current module ->maxbytes (in bytes)
4703 * @return int The maximum size for uploading files.
4704 * @todo Finish documenting this function
4706 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4708 if (! $filesize = ini_get('upload_max_filesize')) {
4709 $filesize = '5M';
4711 $minimumsize = get_real_size($filesize);
4713 if ($postsize = ini_get('post_max_size')) {
4714 $postsize = get_real_size($postsize);
4715 if ($postsize < $minimumsize) {
4716 $minimumsize = $postsize;
4720 if ($sitebytes and $sitebytes < $minimumsize) {
4721 $minimumsize = $sitebytes;
4724 if ($coursebytes and $coursebytes < $minimumsize) {
4725 $minimumsize = $coursebytes;
4728 if ($modulebytes and $modulebytes < $minimumsize) {
4729 $minimumsize = $modulebytes;
4732 return $minimumsize;
4736 * Related to {@link get_max_upload_file_size()} - this function returns an
4737 * array of possible sizes in an array, translated to the
4738 * local language.
4740 * @uses SORT_NUMERIC
4741 * @param int $sizebytes ?
4742 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4743 * @param int $modulebytes Current module ->maxbytes (in bytes)
4744 * @return int
4745 * @todo Finish documenting this function
4747 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4748 global $CFG;
4750 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
4751 return array();
4754 $filesize[$maxsize] = display_size($maxsize);
4756 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
4757 5242880, 10485760, 20971520, 52428800, 104857600);
4759 // Allow maxbytes to be selected if it falls outside the above boundaries
4760 if( isset($CFG->maxbytes) && !in_array($CFG->maxbytes, $sizelist) ){
4761 $sizelist[] = $CFG->maxbytes;
4764 foreach ($sizelist as $sizebytes) {
4765 if ($sizebytes < $maxsize) {
4766 $filesize[$sizebytes] = display_size($sizebytes);
4770 krsort($filesize, SORT_NUMERIC);
4772 return $filesize;
4776 * If there has been an error uploading a file, print the appropriate error message
4777 * Numerical constants used as constant definitions not added until PHP version 4.2.0
4779 * $filearray is a 1-dimensional sub-array of the $_FILES array
4780 * eg $filearray = $_FILES['userfile1']
4781 * If left empty then the first element of the $_FILES array will be used
4783 * @uses $_FILES
4784 * @param array $filearray A 1-dimensional sub-array of the $_FILES array
4785 * @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.
4786 * @return bool|string
4788 function print_file_upload_error($filearray = '', $returnerror = false) {
4790 if ($filearray == '' or !isset($filearray['error'])) {
4792 if (empty($_FILES)) return false;
4794 $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
4795 $filearray = array_shift($files); /// use first element of array
4798 switch ($filearray['error']) {
4800 case 0: // UPLOAD_ERR_OK
4801 if ($filearray['size'] > 0) {
4802 $errmessage = get_string('uploadproblem', $filearray['name']);
4803 } else {
4804 $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
4806 break;
4808 case 1: // UPLOAD_ERR_INI_SIZE
4809 $errmessage = get_string('uploadserverlimit');
4810 break;
4812 case 2: // UPLOAD_ERR_FORM_SIZE
4813 $errmessage = get_string('uploadformlimit');
4814 break;
4816 case 3: // UPLOAD_ERR_PARTIAL
4817 $errmessage = get_string('uploadpartialfile');
4818 break;
4820 case 4: // UPLOAD_ERR_NO_FILE
4821 $errmessage = get_string('uploadnofilefound');
4822 break;
4824 default:
4825 $errmessage = get_string('uploadproblem', $filearray['name']);
4828 if ($returnerror) {
4829 return $errmessage;
4830 } else {
4831 notify($errmessage);
4832 return true;
4838 * handy function to loop through an array of files and resolve any filename conflicts
4839 * both in the array of filenames and for what is already on disk.
4840 * not really compatible with the similar function in uploadlib.php
4841 * but this could be used for files/index.php for moving files around.
4844 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
4845 foreach ($files as $k => $f) {
4846 if (check_potential_filename($destination,$f,$files)) {
4847 $bits = explode('.', $f);
4848 for ($i = 1; true; $i++) {
4849 $try = sprintf($format, $bits[0], $i, $bits[1]);
4850 if (!check_potential_filename($destination,$try,$files)) {
4851 $files[$k] = $try;
4852 break;
4857 return $files;
4861 * @used by resolve_filename_collisions
4863 function check_potential_filename($destination,$filename,$files) {
4864 if (file_exists($destination.'/'.$filename)) {
4865 return true;
4867 if (count(array_keys($files,$filename)) > 1) {
4868 return true;
4870 return false;
4875 * Returns an array with all the filenames in
4876 * all subdirectories, relative to the given rootdir.
4877 * If excludefile is defined, then that file/directory is ignored
4878 * If getdirs is true, then (sub)directories are included in the output
4879 * If getfiles is true, then files are included in the output
4880 * (at least one of these must be true!)
4882 * @param string $rootdir ?
4883 * @param string $excludefile If defined then the specified file/directory is ignored
4884 * @param bool $descend ?
4885 * @param bool $getdirs If true then (sub)directories are included in the output
4886 * @param bool $getfiles If true then files are included in the output
4887 * @return array An array with all the filenames in
4888 * all subdirectories, relative to the given rootdir
4889 * @todo Finish documenting this function. Add examples of $excludefile usage.
4891 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
4893 $dirs = array();
4895 if (!$getdirs and !$getfiles) { // Nothing to show
4896 return $dirs;
4899 if (!is_dir($rootdir)) { // Must be a directory
4900 return $dirs;
4903 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
4904 return $dirs;
4907 if (!is_array($excludefiles)) {
4908 $excludefiles = array($excludefiles);
4911 while (false !== ($file = readdir($dir))) {
4912 $firstchar = substr($file, 0, 1);
4913 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
4914 continue;
4916 $fullfile = $rootdir .'/'. $file;
4917 if (filetype($fullfile) == 'dir') {
4918 if ($getdirs) {
4919 $dirs[] = $file;
4921 if ($descend) {
4922 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
4923 foreach ($subdirs as $subdir) {
4924 $dirs[] = $file .'/'. $subdir;
4927 } else if ($getfiles) {
4928 $dirs[] = $file;
4931 closedir($dir);
4933 asort($dirs);
4935 return $dirs;
4940 * Adds up all the files in a directory and works out the size.
4942 * @param string $rootdir ?
4943 * @param string $excludefile ?
4944 * @return array
4945 * @todo Finish documenting this function
4947 function get_directory_size($rootdir, $excludefile='') {
4949 global $CFG;
4951 // do it this way if we can, it's much faster
4952 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
4953 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
4954 $output = null;
4955 $return = null;
4956 exec($command,$output,$return);
4957 if (is_array($output)) {
4958 return get_real_size(intval($output[0]).'k'); // we told it to return k.
4962 if (!is_dir($rootdir)) { // Must be a directory
4963 return 0;
4966 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
4967 return 0;
4970 $size = 0;
4972 while (false !== ($file = readdir($dir))) {
4973 $firstchar = substr($file, 0, 1);
4974 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
4975 continue;
4977 $fullfile = $rootdir .'/'. $file;
4978 if (filetype($fullfile) == 'dir') {
4979 $size += get_directory_size($fullfile, $excludefile);
4980 } else {
4981 $size += filesize($fullfile);
4984 closedir($dir);
4986 return $size;
4990 * Converts bytes into display form
4992 * @param string $size ?
4993 * @return string
4994 * @staticvar string $gb Localized string for size in gigabytes
4995 * @staticvar string $mb Localized string for size in megabytes
4996 * @staticvar string $kb Localized string for size in kilobytes
4997 * @staticvar string $b Localized string for size in bytes
4998 * @todo Finish documenting this function. Verify return type.
5000 function display_size($size) {
5002 static $gb, $mb, $kb, $b;
5004 if (empty($gb)) {
5005 $gb = get_string('sizegb');
5006 $mb = get_string('sizemb');
5007 $kb = get_string('sizekb');
5008 $b = get_string('sizeb');
5011 if ($size >= 1073741824) {
5012 $size = round($size / 1073741824 * 10) / 10 . $gb;
5013 } else if ($size >= 1048576) {
5014 $size = round($size / 1048576 * 10) / 10 . $mb;
5015 } else if ($size >= 1024) {
5016 $size = round($size / 1024 * 10) / 10 . $kb;
5017 } else {
5018 $size = $size .' '. $b;
5020 return $size;
5024 * Cleans a given filename by removing suspicious or troublesome characters
5025 * Only these are allowed: alphanumeric _ - .
5026 * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
5028 * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
5029 * because native zip binaries do weird character conversions. Use PHP zipping instead.
5031 * @param string $string file name
5032 * @return string cleaned file name
5034 function clean_filename($string) {
5035 global $CFG;
5036 if (empty($CFG->unicodecleanfilename)) {
5037 $textlib = textlib_get_instance();
5038 $string = $textlib->specialtoascii($string);
5039 $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
5040 } else {
5041 //clean only ascii range
5042 $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
5044 $string = preg_replace("/_+/", '_', $string);
5045 $string = preg_replace("/\.\.+/", '.', $string);
5046 return $string;
5050 /// STRING TRANSLATION ////////////////////////////////////////
5053 * Returns the code for the current language
5055 * @uses $CFG
5056 * @param $USER
5057 * @param $SESSION
5058 * @return string
5060 function current_language() {
5061 global $CFG, $USER, $SESSION, $COURSE;
5063 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
5064 $return = $COURSE->lang;
5066 } else if (!empty($SESSION->lang)) { // Session language can override other settings
5067 $return = $SESSION->lang;
5069 } else if (!empty($USER->lang)) {
5070 $return = $USER->lang;
5072 } else {
5073 $return = $CFG->lang;
5076 if ($return == 'en') {
5077 $return = 'en_utf8';
5080 return $return;
5084 * Prints out a translated string.
5086 * Prints out a translated string using the return value from the {@link get_string()} function.
5088 * Example usage of this function when the string is in the moodle.php file:<br/>
5089 * <code>
5090 * echo '<strong>';
5091 * print_string('wordforstudent');
5092 * echo '</strong>';
5093 * </code>
5095 * Example usage of this function when the string is not in the moodle.php file:<br/>
5096 * <code>
5097 * echo '<h1>';
5098 * print_string('typecourse', 'calendar');
5099 * echo '</h1>';
5100 * </code>
5102 * @param string $identifier The key identifier for the localized string
5103 * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
5104 * @param mixed $a An object, string or number that can be used
5105 * within translation strings
5107 function print_string($identifier, $module='', $a=NULL) {
5108 echo get_string($identifier, $module, $a);
5112 * fix up the optional data in get_string()/print_string() etc
5113 * ensure possible sprintf() format characters are escaped correctly
5114 * needs to handle arbitrary strings and objects
5115 * @param mixed $a An object, string or number that can be used
5116 * @return mixed the supplied parameter 'cleaned'
5118 function clean_getstring_data( $a ) {
5119 if (is_string($a)) {
5120 return str_replace( '%','%%',$a );
5122 elseif (is_object($a)) {
5123 $a_vars = get_object_vars( $a );
5124 $new_a_vars = array();
5125 foreach ($a_vars as $fname => $a_var) {
5126 $new_a_vars[$fname] = clean_getstring_data( $a_var );
5128 return (object)$new_a_vars;
5130 else {
5131 return $a;
5136 * @return array places to look for lang strings based on the prefix to the
5137 * module name. For example qtype_ in question/type. Used by get_string and
5138 * help.php.
5140 function places_to_search_for_lang_strings() {
5141 global $CFG;
5143 return array(
5144 '__exceptions' => array('moodle', 'langconfig'),
5145 'assignment_' => array('mod/assignment/type'),
5146 'auth_' => array('auth'),
5147 'block_' => array('blocks'),
5148 'datafield_' => array('mod/data/field'),
5149 'datapreset_' => array('mod/data/preset'),
5150 'enrol_' => array('enrol'),
5151 'filter_' => array('filter'),
5152 'format_' => array('course/format'),
5153 'qtype_' => array('question/type'),
5154 'report_' => array($CFG->admin.'/report', 'course/report', 'mod/quiz/report'),
5155 'resource_' => array('mod/resource/type'),
5156 'gradereport_' => array('grade/report'),
5157 'gradeimport_' => array('grade/import'),
5158 'gradeexport_' => array('grade/export'),
5159 'profilefield_' => array('user/profile/field'),
5160 '' => array('mod')
5165 * Returns a localized string.
5167 * Returns the translated string specified by $identifier as
5168 * for $module. Uses the same format files as STphp.
5169 * $a is an object, string or number that can be used
5170 * within translation strings
5172 * eg "hello \$a->firstname \$a->lastname"
5173 * or "hello \$a"
5175 * If you would like to directly echo the localized string use
5176 * the function {@link print_string()}
5178 * Example usage of this function involves finding the string you would
5179 * like a local equivalent of and using its identifier and module information
5180 * to retrive it.<br/>
5181 * If you open moodle/lang/en/moodle.php and look near line 1031
5182 * you will find a string to prompt a user for their word for student
5183 * <code>
5184 * $string['wordforstudent'] = 'Your word for Student';
5185 * </code>
5186 * So if you want to display the string 'Your word for student'
5187 * in any language that supports it on your site
5188 * you just need to use the identifier 'wordforstudent'
5189 * <code>
5190 * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
5192 * </code>
5193 * If the string you want is in another file you'd take a slightly
5194 * different approach. Looking in moodle/lang/en/calendar.php you find
5195 * around line 75:
5196 * <code>
5197 * $string['typecourse'] = 'Course event';
5198 * </code>
5199 * If you want to display the string "Course event" in any language
5200 * supported you would use the identifier 'typecourse' and the module 'calendar'
5201 * (because it is in the file calendar.php):
5202 * <code>
5203 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
5204 * </code>
5206 * As a last resort, should the identifier fail to map to a string
5207 * the returned string will be [[ $identifier ]]
5209 * @uses $CFG
5210 * @param string $identifier The key identifier for the localized string
5211 * @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.
5212 * @param mixed $a An object, string or number that can be used
5213 * within translation strings
5214 * @param array $extralocations An array of strings with other locations to look for string files
5215 * @return string The localized string.
5217 function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
5219 global $CFG;
5221 /// originally these special strings were stored in moodle.php now we are only in langconfig.php
5222 $langconfigstrs = array('alphabet', 'backupnameformat', 'decsep', 'firstdayofweek', 'listsep', 'locale',
5223 'localewin', 'localewincharset', 'oldcharset',
5224 'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
5225 'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
5226 'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
5227 'thischarset', 'thisdirection', 'thislanguage', 'strftimedatetimeshort');
5229 $filetocheck = 'langconfig.php';
5230 $defaultlang = 'en_utf8';
5231 if (in_array($identifier, $langconfigstrs)) {
5232 $module = 'langconfig'; //This strings are under langconfig.php for 1.6 lang packs
5235 $lang = current_language();
5237 if ($module == '') {
5238 $module = 'moodle';
5241 /// If the "module" is actually a pathname, then automatically derive the proper module name
5242 if (strpos($module, '/') !== false) {
5243 $modulepath = split('/', $module);
5245 switch ($modulepath[0]) {
5247 case 'mod':
5248 $module = $modulepath[1];
5249 break;
5251 case 'blocks':
5252 case 'block':
5253 $module = 'block_'.$modulepath[1];
5254 break;
5256 case 'enrol':
5257 $module = 'enrol_'.$modulepath[1];
5258 break;
5260 case 'format':
5261 $module = 'format_'.$modulepath[1];
5262 break;
5264 case 'grade':
5265 $module = 'grade'.$modulepath[1].'_'.$modulepath[2];
5266 break;
5270 /// if $a happens to have % in it, double it so sprintf() doesn't break
5271 if ($a) {
5272 $a = clean_getstring_data( $a );
5275 /// Define the two or three major locations of language strings for this module
5276 $locations = array();
5278 if (!empty($extralocations)) { // Calling code has a good idea where to look
5279 if (is_array($extralocations)) {
5280 $locations += $extralocations;
5281 } else if (is_string($extralocations)) {
5282 $locations[] = $extralocations;
5283 } else {
5284 debugging('Bad lang path provided');
5288 if (isset($CFG->running_installer)) {
5289 $module = 'installer';
5290 $filetocheck = 'installer.php';
5291 $locations[] = $CFG->dirroot.'/install/lang/';
5292 $locations[] = $CFG->dataroot.'/lang/';
5293 $locations[] = $CFG->dirroot.'/lang/';
5294 $defaultlang = 'en_utf8';
5295 } else {
5296 $locations[] = $CFG->dataroot.'/lang/';
5297 $locations[] = $CFG->dirroot.'/lang/';
5298 $locations[] = $CFG->dirroot.'/local/lang/';
5301 /// Add extra places to look for strings for particular plugin types.
5302 $rules = places_to_search_for_lang_strings();
5303 $exceptions = $rules['__exceptions'];
5304 unset($rules['__exceptions']);
5306 if (!in_array($module, $exceptions)) {
5307 $dividerpos = strpos($module, '_');
5308 if ($dividerpos === false) {
5309 $type = '';
5310 $plugin = $module;
5311 } else {
5312 $type = substr($module, 0, $dividerpos + 1);
5313 $plugin = substr($module, $dividerpos + 1);
5315 if (!empty($rules[$type])) {
5316 foreach ($rules[$type] as $location) {
5317 $locations[] = $CFG->dirroot . "/$location/$plugin/lang/";
5322 /// First check all the normal locations for the string in the current language
5323 $resultstring = '';
5324 foreach ($locations as $location) {
5325 $locallangfile = $location.$lang.'_local'.'/'.$module.'.php'; //first, see if there's a local file
5326 if (file_exists($locallangfile)) {
5327 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5328 if (eval($result) === FALSE) {
5329 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5331 return $resultstring;
5334 //if local directory not found, or particular string does not exist in local direcotry
5335 $langfile = $location.$lang.'/'.$module.'.php';
5336 if (file_exists($langfile)) {
5337 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5338 if (eval($result) === FALSE) {
5339 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5341 return $resultstring;
5346 /// If the preferred language was English (utf8) we can abort now
5347 /// saving some checks beacuse it's the only "root" lang
5348 if ($lang == 'en_utf8') {
5349 return '[['. $identifier .']]';
5352 /// Is a parent language defined? If so, try to find this string in a parent language file
5354 foreach ($locations as $location) {
5355 $langfile = $location.$lang.'/'.$filetocheck;
5356 if (file_exists($langfile)) {
5357 if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
5358 eval($result);
5359 if (!empty($parentlang)) { // found it!
5361 //first, see if there's a local file for parent
5362 $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
5363 if (file_exists($locallangfile)) {
5364 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5365 eval($result);
5366 return $resultstring;
5370 //if local directory not found, or particular string does not exist in local direcotry
5371 $langfile = $location.$parentlang.'/'.$module.'.php';
5372 if (file_exists($langfile)) {
5373 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5374 eval($result);
5375 return $resultstring;
5383 /// Our only remaining option is to try English
5385 foreach ($locations as $location) {
5386 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5387 if (file_exists($locallangfile)) {
5388 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5389 eval($result);
5390 return $resultstring;
5394 //if local_en not found, or string not found in local_en
5395 $langfile = $location.$defaultlang.'/'.$module.'.php';
5397 if (file_exists($langfile)) {
5398 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5399 eval($result);
5400 return $resultstring;
5405 /// And, because under 1.6 en is defined as en_utf8 child, me must try
5406 /// if it hasn't been queried before.
5407 if ($defaultlang == 'en') {
5408 $defaultlang = 'en_utf8';
5409 foreach ($locations as $location) {
5410 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5411 if (file_exists($locallangfile)) {
5412 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5413 eval($result);
5414 return $resultstring;
5418 //if local_en not found, or string not found in local_en
5419 $langfile = $location.$defaultlang.'/'.$module.'.php';
5421 if (file_exists($langfile)) {
5422 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5423 eval($result);
5424 return $resultstring;
5430 return '[['.$identifier.']]'; // Last resort
5434 * This function is only used from {@link get_string()}.
5436 * @internal Only used from get_string, not meant to be public API
5437 * @param string $identifier ?
5438 * @param string $langfile ?
5439 * @param string $destination ?
5440 * @return string|false ?
5441 * @staticvar array $strings Localized strings
5442 * @access private
5443 * @todo Finish documenting this function.
5445 function get_string_from_file($identifier, $langfile, $destination) {
5447 static $strings; // Keep the strings cached in memory.
5449 if (empty($strings[$langfile])) {
5450 $string = array();
5451 include ($langfile);
5452 $strings[$langfile] = $string;
5453 } else {
5454 $string = &$strings[$langfile];
5457 if (!isset ($string[$identifier])) {
5458 return false;
5461 return $destination .'= sprintf("'. $string[$identifier] .'");';
5465 * Converts an array of strings to their localized value.
5467 * @param array $array An array of strings
5468 * @param string $module The language module that these strings can be found in.
5469 * @return string
5471 function get_strings($array, $module='') {
5473 $string = NULL;
5474 foreach ($array as $item) {
5475 $string->$item = get_string($item, $module);
5477 return $string;
5481 * Returns a list of language codes and their full names
5482 * hides the _local files from everyone.
5483 * @param bool refreshcache force refreshing of lang cache
5484 * @param bool returnall ignore langlist, return all languages available
5485 * @return array An associative array with contents in the form of LanguageCode => LanguageName
5487 function get_list_of_languages($refreshcache=false, $returnall=false) {
5489 global $CFG;
5491 $languages = array();
5493 $filetocheck = 'langconfig.php';
5495 if (!$refreshcache && !$returnall && !empty($CFG->langcache) && file_exists($CFG->dataroot .'/cache/languages')) {
5496 /// read available langs from cache
5498 $lines = file($CFG->dataroot .'/cache/languages');
5499 foreach ($lines as $line) {
5500 $line = trim($line);
5501 if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
5502 $languages[$matches[1]] = $matches[2];
5505 unset($lines); unset($line); unset($matches);
5506 return $languages;
5509 if (!$returnall && !empty($CFG->langlist)) {
5510 /// return only languages allowed in langlist admin setting
5512 $langlist = explode(',', $CFG->langlist);
5513 // fix short lang names first - non existing langs are skipped anyway...
5514 foreach ($langlist as $lang) {
5515 if (strpos($lang, '_utf8') === false) {
5516 $langlist[] = $lang.'_utf8';
5519 // find existing langs from langlist
5520 foreach ($langlist as $lang) {
5521 $lang = trim($lang); //Just trim spaces to be a bit more permissive
5522 if (strstr($lang, '_local')!==false) {
5523 continue;
5525 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5526 $shortlang = substr($lang, 0, -5);
5527 } else {
5528 $shortlang = $lang;
5530 /// Search under dirroot/lang
5531 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5532 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5533 if (!empty($string['thislanguage'])) {
5534 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5536 unset($string);
5538 /// And moodledata/lang
5539 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5540 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5541 if (!empty($string['thislanguage'])) {
5542 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5544 unset($string);
5548 } else {
5549 /// return all languages available in system
5550 /// Fetch langs from moodle/lang directory
5551 $langdirs = get_list_of_plugins('lang');
5552 /// Fetch langs from moodledata/lang directory
5553 $langdirs2 = get_list_of_plugins('lang', '', $CFG->dataroot);
5554 /// Merge both lists of langs
5555 $langdirs = array_merge($langdirs, $langdirs2);
5556 /// Sort all
5557 asort($langdirs);
5558 /// Get some info from each lang (first from moodledata, then from moodle)
5559 foreach ($langdirs as $lang) {
5560 if (strstr($lang, '_local')!==false) {
5561 continue;
5563 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5564 $shortlang = substr($lang, 0, -5);
5565 } else {
5566 $shortlang = $lang;
5568 /// Search under moodledata/lang
5569 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5570 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5571 if (!empty($string['thislanguage'])) {
5572 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5574 unset($string);
5576 /// And dirroot/lang
5577 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5578 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5579 if (!empty($string['thislanguage'])) {
5580 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5582 unset($string);
5587 if ($refreshcache && !empty($CFG->langcache)) {
5588 if ($returnall) {
5589 // we have a list of all langs only, just delete old cache
5590 @unlink($CFG->dataroot.'/cache/languages');
5592 } else {
5593 // store the list of allowed languages
5594 if ($file = fopen($CFG->dataroot .'/cache/languages', 'w')) {
5595 foreach ($languages as $key => $value) {
5596 fwrite($file, "$key $value\n");
5598 fclose($file);
5603 return $languages;
5607 * Returns a list of charset codes. It's hardcoded, so they should be added manually
5608 * (cheking that such charset is supported by the texlib library!)
5610 * @return array And associative array with contents in the form of charset => charset
5612 function get_list_of_charsets() {
5614 $charsets = array(
5615 'EUC-JP' => 'EUC-JP',
5616 'ISO-2022-JP'=> 'ISO-2022-JP',
5617 'ISO-8859-1' => 'ISO-8859-1',
5618 'SHIFT-JIS' => 'SHIFT-JIS',
5619 'GB2312' => 'GB2312',
5620 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
5621 'UTF-8' => 'UTF-8');
5623 asort($charsets);
5625 return $charsets;
5629 * Returns a list of country names in the current language
5631 * @uses $CFG
5632 * @uses $USER
5633 * @return array
5635 function get_list_of_countries() {
5636 global $CFG, $USER;
5638 $lang = current_language();
5640 if (!file_exists($CFG->dirroot .'/lang/'. $lang .'/countries.php') &&
5641 !file_exists($CFG->dataroot.'/lang/'. $lang .'/countries.php')) {
5642 if ($parentlang = get_string('parentlanguage')) {
5643 if (file_exists($CFG->dirroot .'/lang/'. $parentlang .'/countries.php') ||
5644 file_exists($CFG->dataroot.'/lang/'. $parentlang .'/countries.php')) {
5645 $lang = $parentlang;
5646 } else {
5647 $lang = 'en_utf8'; // countries.php must exist in this pack
5649 } else {
5650 $lang = 'en_utf8'; // countries.php must exist in this pack
5654 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/countries.php')) {
5655 include($CFG->dataroot .'/lang/'. $lang .'/countries.php');
5656 } else if (file_exists($CFG->dirroot .'/lang/'. $lang .'/countries.php')) {
5657 include($CFG->dirroot .'/lang/'. $lang .'/countries.php');
5660 if (!empty($string)) {
5661 uasort($string, 'strcoll');
5664 return $string;
5668 * Returns a list of valid and compatible themes
5670 * @uses $CFG
5671 * @return array
5673 function get_list_of_themes() {
5675 global $CFG;
5677 $themes = array();
5679 if (!empty($CFG->themelist)) { // use admin's list of themes
5680 $themelist = explode(',', $CFG->themelist);
5681 } else {
5682 $themelist = get_list_of_plugins("theme");
5685 foreach ($themelist as $key => $theme) {
5686 if (!file_exists("$CFG->themedir/$theme/config.php")) { // bad folder
5687 continue;
5689 $THEME = new object(); // Note this is not the global one!! :-)
5690 include("$CFG->themedir/$theme/config.php");
5691 if (!isset($THEME->sheets)) { // Not a valid 1.5 theme
5692 continue;
5694 $themes[$theme] = $theme;
5696 asort($themes);
5698 return $themes;
5703 * Returns a list of picture names in the current or specified language
5705 * @uses $CFG
5706 * @return array
5708 function get_list_of_pixnames($lang = '') {
5709 global $CFG;
5711 if (empty($lang)) {
5712 $lang = current_language();
5715 $string = array();
5717 $path = $CFG->dirroot .'/lang/en_utf8/pix.php'; // always exists
5719 if (file_exists($CFG->dataroot .'/lang/'. $lang .'_local/pix.php')) {
5720 $path = $CFG->dataroot .'/lang/'. $lang .'_local/pix.php';
5722 } else if (file_exists($CFG->dirroot .'/lang/'. $lang .'/pix.php')) {
5723 $path = $CFG->dirroot .'/lang/'. $lang .'/pix.php';
5725 } else if (file_exists($CFG->dataroot .'/lang/'. $lang .'/pix.php')) {
5726 $path = $CFG->dataroot .'/lang/'. $lang .'/pix.php';
5728 } else if ($parentlang = get_string('parentlanguage') and $parentlang != '[[parentlanguage]]') {
5729 return get_list_of_pixnames($parentlang); //return pixnames from parent language instead
5732 include($path);
5734 return $string;
5738 * Returns a list of timezones in the current language
5740 * @uses $CFG
5741 * @return array
5743 function get_list_of_timezones() {
5744 global $CFG;
5746 static $timezones;
5748 if (!empty($timezones)) { // This function has been called recently
5749 return $timezones;
5752 $timezones = array();
5754 if ($rawtimezones = get_records_sql('SELECT MAX(id), name FROM '.$CFG->prefix.'timezone GROUP BY name')) {
5755 foreach($rawtimezones as $timezone) {
5756 if (!empty($timezone->name)) {
5757 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
5758 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
5759 $timezones[$timezone->name] = $timezone->name;
5765 asort($timezones);
5767 for ($i = -13; $i <= 13; $i += .5) {
5768 $tzstring = 'UTC';
5769 if ($i < 0) {
5770 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
5771 } else if ($i > 0) {
5772 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
5773 } else {
5774 $timezones[sprintf("%.1f", $i)] = $tzstring;
5778 return $timezones;
5782 * Returns a list of currencies in the current language
5784 * @uses $CFG
5785 * @uses $USER
5786 * @return array
5788 function get_list_of_currencies() {
5789 global $CFG, $USER;
5791 $lang = current_language();
5793 if (!file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5794 if ($parentlang = get_string('parentlanguage')) {
5795 if (file_exists($CFG->dataroot .'/lang/'. $parentlang .'/currencies.php')) {
5796 $lang = $parentlang;
5797 } else {
5798 $lang = 'en_utf8'; // currencies.php must exist in this pack
5800 } else {
5801 $lang = 'en_utf8'; // currencies.php must exist in this pack
5805 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5806 include_once($CFG->dataroot .'/lang/'. $lang .'/currencies.php');
5807 } else { //if en_utf8 is not installed in dataroot
5808 include_once($CFG->dirroot .'/lang/'. $lang .'/currencies.php');
5811 if (!empty($string)) {
5812 asort($string);
5815 return $string;
5819 /// ENCRYPTION ////////////////////////////////////////////////
5822 * rc4encrypt
5824 * @param string $data ?
5825 * @return string
5826 * @todo Finish documenting this function
5828 function rc4encrypt($data) {
5829 $password = 'nfgjeingjk';
5830 return endecrypt($password, $data, '');
5834 * rc4decrypt
5836 * @param string $data ?
5837 * @return string
5838 * @todo Finish documenting this function
5840 function rc4decrypt($data) {
5841 $password = 'nfgjeingjk';
5842 return endecrypt($password, $data, 'de');
5846 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
5848 * @param string $pwd ?
5849 * @param string $data ?
5850 * @param string $case ?
5851 * @return string
5852 * @todo Finish documenting this function
5854 function endecrypt ($pwd, $data, $case) {
5856 if ($case == 'de') {
5857 $data = urldecode($data);
5860 $key[] = '';
5861 $box[] = '';
5862 $temp_swap = '';
5863 $pwd_length = 0;
5865 $pwd_length = strlen($pwd);
5867 for ($i = 0; $i <= 255; $i++) {
5868 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
5869 $box[$i] = $i;
5872 $x = 0;
5874 for ($i = 0; $i <= 255; $i++) {
5875 $x = ($x + $box[$i] + $key[$i]) % 256;
5876 $temp_swap = $box[$i];
5877 $box[$i] = $box[$x];
5878 $box[$x] = $temp_swap;
5881 $temp = '';
5882 $k = '';
5884 $cipherby = '';
5885 $cipher = '';
5887 $a = 0;
5888 $j = 0;
5890 for ($i = 0; $i < strlen($data); $i++) {
5891 $a = ($a + 1) % 256;
5892 $j = ($j + $box[$a]) % 256;
5893 $temp = $box[$a];
5894 $box[$a] = $box[$j];
5895 $box[$j] = $temp;
5896 $k = $box[(($box[$a] + $box[$j]) % 256)];
5897 $cipherby = ord(substr($data, $i, 1)) ^ $k;
5898 $cipher .= chr($cipherby);
5901 if ($case == 'de') {
5902 $cipher = urldecode(urlencode($cipher));
5903 } else {
5904 $cipher = urlencode($cipher);
5907 return $cipher;
5911 /// CALENDAR MANAGEMENT ////////////////////////////////////////////////////////////////
5915 * Call this function to add an event to the calendar table
5916 * and to call any calendar plugins
5918 * @uses $CFG
5919 * @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:
5920 * <ul>
5921 * <li><b>$event->name</b> - Name for the event
5922 * <li><b>$event->description</b> - Description of the event (defaults to '')
5923 * <li><b>$event->format</b> - Format for the description (using formatting types defined at the top of weblib.php)
5924 * <li><b>$event->courseid</b> - The id of the course this event belongs to (0 = all courses)
5925 * <li><b>$event->groupid</b> - The id of the group this event belongs to (0 = no group)
5926 * <li><b>$event->userid</b> - The id of the user this event belongs to (0 = no user)
5927 * <li><b>$event->modulename</b> - Name of the module that creates this event
5928 * <li><b>$event->instance</b> - Instance of the module that owns this event
5929 * <li><b>$event->eventtype</b> - The type info together with the module info could
5930 * be used by calendar plugins to decide how to display event
5931 * <li><b>$event->timestart</b>- Timestamp for start of event
5932 * <li><b>$event->timeduration</b> - Duration (defaults to zero)
5933 * <li><b>$event->visible</b> - 0 if the event should be hidden (e.g. because the activity that created it is hidden)
5934 * </ul>
5935 * @return int The id number of the resulting record
5937 function add_event($event) {
5939 global $CFG;
5941 $event->timemodified = time();
5943 if (!$event->id = insert_record('event', $event)) {
5944 return false;
5947 if (!empty($CFG->calendar)) { // call the add_event function of the selected calendar
5948 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
5949 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
5950 $calendar_add_event = $CFG->calendar.'_add_event';
5951 if (function_exists($calendar_add_event)) {
5952 $calendar_add_event($event);
5957 return $event->id;
5961 * Call this function to update an event in the calendar table
5962 * the event will be identified by the id field of the $event object.
5964 * @uses $CFG
5965 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
5966 * @return bool
5968 function update_event($event) {
5970 global $CFG;
5972 $event->timemodified = time();
5974 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
5975 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
5976 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
5977 $calendar_update_event = $CFG->calendar.'_update_event';
5978 if (function_exists($calendar_update_event)) {
5979 $calendar_update_event($event);
5983 return update_record('event', $event);
5987 * Call this function to delete the event with id $id from calendar table.
5989 * @uses $CFG
5990 * @param int $id The id of an event from the 'calendar' table.
5991 * @return array An associative array with the results from the SQL call.
5992 * @todo Verify return type
5994 function delete_event($id) {
5996 global $CFG;
5998 if (!empty($CFG->calendar)) { // call the delete_event function of the selected calendar
5999 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6000 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6001 $calendar_delete_event = $CFG->calendar.'_delete_event';
6002 if (function_exists($calendar_delete_event)) {
6003 $calendar_delete_event($id);
6007 return delete_records('event', 'id', $id);
6011 * Call this function to hide an event in the calendar table
6012 * the event will be identified by the id field of the $event object.
6014 * @uses $CFG
6015 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6016 * @return array An associative array with the results from the SQL call.
6017 * @todo Verify return type
6019 function hide_event($event) {
6020 global $CFG;
6022 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6023 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6024 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6025 $calendar_hide_event = $CFG->calendar.'_hide_event';
6026 if (function_exists($calendar_hide_event)) {
6027 $calendar_hide_event($event);
6031 return set_field('event', 'visible', 0, 'id', $event->id);
6035 * Call this function to unhide an event in the calendar table
6036 * the event will be identified by the id field of the $event object.
6038 * @uses $CFG
6039 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6040 * @return array An associative array with the results from the SQL call.
6041 * @todo Verify return type
6043 function show_event($event) {
6044 global $CFG;
6046 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6047 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6048 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6049 $calendar_show_event = $CFG->calendar.'_show_event';
6050 if (function_exists($calendar_show_event)) {
6051 $calendar_show_event($event);
6055 return set_field('event', 'visible', 1, 'id', $event->id);
6059 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6062 * Lists plugin directories within some directory
6064 * @uses $CFG
6065 * @param string $plugin dir under we'll look for plugins (defaults to 'mod')
6066 * @param string $exclude dir name to exclude from the list (defaults to none)
6067 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
6068 * @return array of plugins found under the requested parameters
6070 function get_list_of_plugins($plugin='mod', $exclude='', $basedir='') {
6072 global $CFG;
6074 $plugins = array();
6076 if (empty($basedir)) {
6078 # This switch allows us to use the appropiate theme directory - and potentialy alternatives for other plugins
6079 switch ($plugin) {
6080 case "theme":
6081 $basedir = $CFG->themedir;
6082 break;
6084 default:
6085 $basedir = $CFG->dirroot .'/'. $plugin;
6088 } else {
6089 $basedir = $basedir .'/'. $plugin;
6092 if (file_exists($basedir) && filetype($basedir) == 'dir') {
6093 $dirhandle = opendir($basedir);
6094 while (false !== ($dir = readdir($dirhandle))) {
6095 $firstchar = substr($dir, 0, 1);
6096 if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == $exclude) {
6097 continue;
6099 if (filetype($basedir .'/'. $dir) != 'dir') {
6100 continue;
6102 $plugins[] = $dir;
6104 closedir($dirhandle);
6106 if ($plugins) {
6107 asort($plugins);
6109 return $plugins;
6113 * Returns true if the current version of PHP is greater that the specified one.
6115 * @param string $version The version of php being tested.
6116 * @return bool
6118 function check_php_version($version='4.1.0') {
6119 return (version_compare(phpversion(), $version) >= 0);
6123 * Checks to see if is the browser operating system matches the specified
6124 * brand.
6126 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
6128 * @uses $_SERVER
6129 * @param string $brand The operating system identifier being tested
6130 * @return bool true if the given brand below to the detected operating system
6132 function check_browser_operating_system($brand) {
6133 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6134 return false;
6137 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
6138 return true;
6141 return false;
6145 * Checks to see if is a browser matches the specified
6146 * brand and is equal or better version.
6148 * @uses $_SERVER
6149 * @param string $brand The browser identifier being tested
6150 * @param int $version The version of the browser
6151 * @return bool true if the given version is below that of the detected browser
6153 function check_browser_version($brand='MSIE', $version=5.5) {
6154 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6155 return false;
6158 $agent = $_SERVER['HTTP_USER_AGENT'];
6160 switch ($brand) {
6162 case 'Camino': /// Mozilla Firefox browsers
6164 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
6165 if (version_compare($match[1], $version) >= 0) {
6166 return true;
6169 break;
6172 case 'Firefox': /// Mozilla Firefox browsers
6174 if (preg_match("/Firefox\/([0-9\.]+)/i", $agent, $match)) {
6175 if (version_compare($match[1], $version) >= 0) {
6176 return true;
6179 break;
6182 case 'Gecko': /// Gecko based browsers
6184 if (substr_count($agent, 'Camino')) {
6185 // MacOS X Camino support
6186 $version = 20041110;
6189 // the proper string - Gecko/CCYYMMDD Vendor/Version
6190 // Faster version and work-a-round No IDN problem.
6191 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
6192 if ($match[1] > $version) {
6193 return true;
6196 break;
6199 case 'MSIE': /// Internet Explorer
6201 if (strpos($agent, 'Opera')) { // Reject Opera
6202 return false;
6204 $string = explode(';', $agent);
6205 if (!isset($string[1])) {
6206 return false;
6208 $string = explode(' ', trim($string[1]));
6209 if (!isset($string[0]) and !isset($string[1])) {
6210 return false;
6212 if ($string[0] == $brand and (float)$string[1] >= $version ) {
6213 return true;
6215 break;
6217 case 'Opera': /// Opera
6219 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
6220 if (version_compare($match[1], $version) >= 0) {
6221 return true;
6224 break;
6226 case 'Safari': /// Safari
6227 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SimbianOS
6228 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
6229 return false;
6230 } elseif (strpos($agent, 'Shiira')) { // Reject Shiira
6231 return false;
6232 } elseif (strpos($agent, 'SimbianOS')) { // Reject SimbianOS
6233 return false;
6236 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
6237 if (version_compare($match[1], $version) >= 0) {
6238 return true;
6242 break;
6246 return false;
6250 * This function makes the return value of ini_get consistent if you are
6251 * setting server directives through the .htaccess file in apache.
6252 * Current behavior for value set from php.ini On = 1, Off = [blank]
6253 * Current behavior for value set from .htaccess On = On, Off = Off
6254 * Contributed by jdell @ unr.edu
6256 * @param string $ini_get_arg ?
6257 * @return bool
6258 * @todo Finish documenting this function
6260 function ini_get_bool($ini_get_arg) {
6261 $temp = ini_get($ini_get_arg);
6263 if ($temp == '1' or strtolower($temp) == 'on') {
6264 return true;
6266 return false;
6270 * Compatibility stub to provide backward compatibility
6272 * Determines if the HTML editor is enabled.
6273 * @deprecated Use {@link can_use_html_editor()} instead.
6275 function can_use_richtext_editor() {
6276 return can_use_html_editor();
6280 * Determines if the HTML editor is enabled.
6282 * This depends on site and user
6283 * settings, as well as the current browser being used.
6285 * @return string|false Returns false if editor is not being used, otherwise
6286 * returns 'MSIE' or 'Gecko'.
6288 function can_use_html_editor() {
6289 global $USER, $CFG;
6291 if (!empty($USER->htmleditor) and !empty($CFG->htmleditor)) {
6292 if (check_browser_version('MSIE', 5.5)) {
6293 return 'MSIE';
6294 } else if (check_browser_version('Gecko', 20030516)) {
6295 return 'Gecko';
6298 return false;
6302 * Hack to find out the GD version by parsing phpinfo output
6304 * @return int GD version (1, 2, or 0)
6306 function check_gd_version() {
6307 $gdversion = 0;
6309 if (function_exists('gd_info')){
6310 $gd_info = gd_info();
6311 if (substr_count($gd_info['GD Version'], '2.')) {
6312 $gdversion = 2;
6313 } else if (substr_count($gd_info['GD Version'], '1.')) {
6314 $gdversion = 1;
6317 } else {
6318 ob_start();
6319 phpinfo(INFO_MODULES);
6320 $phpinfo = ob_get_contents();
6321 ob_end_clean();
6323 $phpinfo = explode("\n", $phpinfo);
6326 foreach ($phpinfo as $text) {
6327 $parts = explode('</td>', $text);
6328 foreach ($parts as $key => $val) {
6329 $parts[$key] = trim(strip_tags($val));
6331 if ($parts[0] == 'GD Version') {
6332 if (substr_count($parts[1], '2.0')) {
6333 $parts[1] = '2.0';
6335 $gdversion = intval($parts[1]);
6340 return $gdversion; // 1, 2 or 0
6344 * Determine if moodle installation requires update
6346 * Checks version numbers of main code and all modules to see
6347 * if there are any mismatches
6349 * @uses $CFG
6350 * @return bool
6352 function moodle_needs_upgrading() {
6353 global $CFG;
6355 $version = null;
6356 include_once($CFG->dirroot .'/version.php'); # defines $version and upgrades
6357 if ($CFG->version) {
6358 if ($version > $CFG->version) {
6359 return true;
6361 if ($mods = get_list_of_plugins('mod')) {
6362 foreach ($mods as $mod) {
6363 $fullmod = $CFG->dirroot .'/mod/'. $mod;
6364 $module = new object();
6365 if (!is_readable($fullmod .'/version.php')) {
6366 notify('Module "'. $mod .'" is not readable - check permissions');
6367 continue;
6369 include_once($fullmod .'/version.php'); # defines $module with version etc
6370 if ($currmodule = get_record('modules', 'name', $mod)) {
6371 if ($module->version > $currmodule->version) {
6372 return true;
6377 } else {
6378 return true;
6380 return false;
6384 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
6387 * Notify admin users or admin user of any failed logins (since last notification).
6389 * Note that this function must be only executed from the cron script
6390 * It uses the cache_flags system to store temporary records, deleting them
6391 * by name before finishing
6393 * @uses $CFG
6394 * @uses $db
6395 * @uses HOURSECS
6397 function notify_login_failures() {
6398 global $CFG, $db;
6400 switch ($CFG->notifyloginfailures) {
6401 case 'mainadmin' :
6402 $recip = array(get_admin());
6403 break;
6404 case 'alladmins':
6405 $recip = get_admins();
6406 break;
6409 if (empty($CFG->lastnotifyfailure)) {
6410 $CFG->lastnotifyfailure=0;
6413 // we need to deal with the threshold stuff first.
6414 if (empty($CFG->notifyloginthreshold)) {
6415 $CFG->notifyloginthreshold = 10; // default to something sensible.
6418 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
6419 /// and insert them into the cache_flags temp table
6420 $iprs = get_recordset_sql("SELECT ip, count(*)
6421 FROM {$CFG->prefix}log
6422 WHERE module = 'login'
6423 AND action = 'error'
6424 AND time > $CFG->lastnotifyfailure
6425 GROUP BY ip
6426 HAVING count(*) >= $CFG->notifyloginthreshold");
6427 while ($iprec = rs_fetch_next_record($iprs)) {
6428 if (!empty($iprec->ip)) {
6429 set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
6432 rs_close($iprs);
6434 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
6435 /// and insert them into the cache_flags temp table
6436 $infors = get_recordset_sql("SELECT info, count(*)
6437 FROM {$CFG->prefix}log
6438 WHERE module = 'login'
6439 AND action = 'error'
6440 AND time > $CFG->lastnotifyfailure
6441 GROUP BY info
6442 HAVING count(*) >= $CFG->notifyloginthreshold");
6443 while ($inforec = rs_fetch_next_record($infors)) {
6444 if (!empty($inforec->info)) {
6445 set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
6448 rs_close($infors);
6450 /// Now, select all the login error logged records belonging to the ips and infos
6451 /// since lastnotifyfailure, that we have stored in the cache_flags table
6452 $logsrs = get_recordset_sql("SELECT l.*, u.firstname, u.lastname
6453 FROM {$CFG->prefix}log l
6454 JOIN {$CFG->prefix}cache_flags cf ON (l.ip = cf.name)
6455 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6456 WHERE l.module = 'login'
6457 AND l.action = 'error'
6458 AND l.time > $CFG->lastnotifyfailure
6459 AND cf.flagtype = 'login_failure_by_ip'
6460 UNION ALL
6461 SELECT l.*, u.firstname, u.lastname
6462 FROM {$CFG->prefix}log l
6463 JOIN {$CFG->prefix}cache_flags cf ON (l.info = cf.name)
6464 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6465 WHERE l.module = 'login'
6466 AND l.action = 'error'
6467 AND l.time > $CFG->lastnotifyfailure
6468 AND cf.flagtype = 'login_failure_by_info'
6469 ORDER BY time DESC");
6471 /// Init some variables
6472 $count = 0;
6473 $messages = '';
6474 /// Iterate over the logs recordset
6475 while ($log = rs_fetch_next_record($logsrs)) {
6476 $log->time = userdate($log->time);
6477 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
6478 $count++;
6480 rs_close($logsrs);
6482 /// If we haven't run in the last hour and
6483 /// we have something useful to report and we
6484 /// are actually supposed to be reporting to somebody
6485 if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
6486 $site = get_site();
6487 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
6488 /// Calculate the complete body of notification (start + messages + end)
6489 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
6490 (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
6491 $messages .
6492 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
6494 /// For each destination, send mail
6495 foreach ($recip as $admin) {
6496 mtrace('Emailing '. $admin->username .' about '. $count .' failed login attempts');
6497 email_to_user($admin,get_admin(), $subject, $body);
6500 /// Update lastnotifyfailure with current time
6501 set_config('lastnotifyfailure', time());
6504 /// Finally, delete all the temp records we have created in cache_flags
6505 delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
6509 * moodle_setlocale
6511 * @uses $CFG
6512 * @param string $locale ?
6513 * @todo Finish documenting this function
6515 function moodle_setlocale($locale='') {
6517 global $CFG;
6519 static $currentlocale = ''; // last locale caching
6521 $oldlocale = $currentlocale;
6523 /// Fetch the correct locale based on ostype
6524 if($CFG->ostype == 'WINDOWS') {
6525 $stringtofetch = 'localewin';
6526 } else {
6527 $stringtofetch = 'locale';
6530 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
6531 if (!empty($locale)) {
6532 $currentlocale = $locale;
6533 } else if (!empty($CFG->locale)) { // override locale for all language packs
6534 $currentlocale = $CFG->locale;
6535 } else {
6536 $currentlocale = get_string($stringtofetch);
6539 /// do nothing if locale already set up
6540 if ($oldlocale == $currentlocale) {
6541 return;
6544 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
6545 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
6546 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
6548 /// Get current values
6549 $monetary= setlocale (LC_MONETARY, 0);
6550 $numeric = setlocale (LC_NUMERIC, 0);
6551 $ctype = setlocale (LC_CTYPE, 0);
6552 if ($CFG->ostype != 'WINDOWS') {
6553 $messages= setlocale (LC_MESSAGES, 0);
6555 /// Set locale to all
6556 setlocale (LC_ALL, $currentlocale);
6557 /// Set old values
6558 setlocale (LC_MONETARY, $monetary);
6559 setlocale (LC_NUMERIC, $numeric);
6560 if ($CFG->ostype != 'WINDOWS') {
6561 setlocale (LC_MESSAGES, $messages);
6563 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
6564 setlocale (LC_CTYPE, $ctype);
6569 * Converts string to lowercase using most compatible function available.
6571 * @param string $string The string to convert to all lowercase characters.
6572 * @param string $encoding The encoding on the string.
6573 * @return string
6574 * @todo Add examples of calling this function with/without encoding types
6575 * @deprecated Use textlib->strtolower($text) instead.
6577 function moodle_strtolower ($string, $encoding='') {
6579 //If not specified use utf8
6580 if (empty($encoding)) {
6581 $encoding = 'UTF-8';
6583 //Use text services
6584 $textlib = textlib_get_instance();
6586 return $textlib->strtolower($string, $encoding);
6590 * Count words in a string.
6592 * Words are defined as things between whitespace.
6594 * @param string $string The text to be searched for words.
6595 * @return int The count of words in the specified string
6597 function count_words($string) {
6598 $string = strip_tags($string);
6599 return count(preg_split("/\w\b/", $string)) - 1;
6602 /** Count letters in a string.
6604 * Letters are defined as chars not in tags and different from whitespace.
6606 * @param string $string The text to be searched for letters.
6607 * @return int The count of letters in the specified text.
6609 function count_letters($string) {
6610 /// Loading the textlib singleton instance. We are going to need it.
6611 $textlib = textlib_get_instance();
6613 $string = strip_tags($string); // Tags are out now
6614 $string = ereg_replace('[[:space:]]*','',$string); //Whitespace are out now
6616 return $textlib->strlen($string);
6620 * Generate and return a random string of the specified length.
6622 * @param int $length The length of the string to be created.
6623 * @return string
6625 function random_string ($length=15) {
6626 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
6627 $pool .= 'abcdefghijklmnopqrstuvwxyz';
6628 $pool .= '0123456789';
6629 $poollen = strlen($pool);
6630 mt_srand ((double) microtime() * 1000000);
6631 $string = '';
6632 for ($i = 0; $i < $length; $i++) {
6633 $string .= substr($pool, (mt_rand()%($poollen)), 1);
6635 return $string;
6639 * Given some text (which may contain HTML) and an ideal length,
6640 * this function truncates the text neatly on a word boundary if possible
6641 * @param string $text - text to be shortened
6642 * @param int $ideal - ideal string length
6643 * @param boolean $exact if false, $text will not be cut mid-word
6644 * @return string $truncate - shortened string
6647 function shorten_text($text, $ideal=30, $exact = false) {
6649 global $CFG;
6650 $ending = '...';
6652 // if the plain text is shorter than the maximum length, return the whole text
6653 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
6654 return $text;
6657 // splits all html-tags to scanable lines
6658 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
6660 $total_length = strlen($ending);
6661 $open_tags = array();
6662 $truncate = '';
6664 foreach ($lines as $line_matchings) {
6665 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
6666 if (!empty($line_matchings[1])) {
6667 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
6668 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
6669 // do nothing
6670 // if tag is a closing tag (f.e. </b>)
6671 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
6672 // delete tag from $open_tags list
6673 $pos = array_search($tag_matchings[1], array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
6674 if ($pos !== false) {
6675 unset($open_tags[$pos]);
6677 // if tag is an opening tag (f.e. <b>)
6678 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
6679 // add tag to the beginning of $open_tags list
6680 array_unshift($open_tags, strtolower($tag_matchings[1]));
6682 // add html-tag to $truncate'd text
6683 $truncate .= $line_matchings[1];
6686 // calculate the length of the plain text part of the line; handle entities as one character
6687 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
6688 if ($total_length+$content_length > $ideal) {
6689 // the number of characters which are left
6690 $left = $ideal - $total_length;
6691 $entities_length = 0;
6692 // search for html entities
6693 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)) {
6694 // calculate the real length of all entities in the legal range
6695 foreach ($entities[0] as $entity) {
6696 if ($entity[1]+1-$entities_length <= $left) {
6697 $left--;
6698 $entities_length += strlen($entity[0]);
6699 } else {
6700 // no more characters left
6701 break;
6705 $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
6706 // maximum lenght is reached, so get off the loop
6707 break;
6708 } else {
6709 $truncate .= $line_matchings[2];
6710 $total_length += $content_length;
6713 // if the maximum length is reached, get off the loop
6714 if($total_length >= $ideal) {
6715 break;
6719 // if the words shouldn't be cut in the middle...
6720 if (!$exact) {
6721 // ...search the last occurance of a space...
6722 for ($k=strlen($truncate);$k>0;$k--) {
6723 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
6724 if ($char == '.' or $char == ' ') {
6725 $breakpos = $k+1;
6726 break;
6727 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
6728 $breakpos = $k; // can be truncated at any UTF-8
6729 break; // character boundary.
6734 if (isset($breakpos)) {
6735 // ...and cut the text in this position
6736 $truncate = substr($truncate, 0, $breakpos);
6740 // add the defined ending to the text
6741 $truncate .= $ending;
6743 // close all unclosed html-tags
6744 foreach ($open_tags as $tag) {
6745 $truncate .= '</' . $tag . '>';
6748 return $truncate;
6753 * Given dates in seconds, how many weeks is the date from startdate
6754 * The first week is 1, the second 2 etc ...
6756 * @uses WEEKSECS
6757 * @param ? $startdate ?
6758 * @param ? $thedate ?
6759 * @return string
6760 * @todo Finish documenting this function
6762 function getweek ($startdate, $thedate) {
6763 if ($thedate < $startdate) { // error
6764 return 0;
6767 return floor(($thedate - $startdate) / WEEKSECS) + 1;
6771 * returns a randomly generated password of length $maxlen. inspired by
6772 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
6773 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
6775 * @param int $maxlen The maximum size of the password being generated.
6776 * @return string
6778 function generate_password($maxlen=10) {
6779 global $CFG;
6781 if (empty($CFG->passwordpolicy)) {
6782 $fillers = PASSWORD_DIGITS;
6783 $wordlist = file($CFG->wordlist);
6784 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
6785 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
6786 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
6787 $password = $word1 . $filler1 . $word2;
6788 } else {
6789 $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
6790 $digits = $CFG->minpassworddigits;
6791 $lower = $CFG->minpasswordlower;
6792 $upper = $CFG->minpasswordupper;
6793 $nonalphanum = $CFG->minpasswordnonalphanum;
6794 $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
6796 // Make sure we have enough characters to fulfill
6797 // complexity requirements
6798 $passworddigits = PASSWORD_DIGITS;
6799 while ($digits > strlen($passworddigits)) {
6800 $passworddigits .= PASSWORD_DIGITS;
6802 $passwordlower = PASSWORD_LOWER;
6803 while ($lower > strlen($passwordlower)) {
6804 $passwordlower .= PASSWORD_LOWER;
6806 $passwordupper = PASSWORD_UPPER;
6807 while ($upper > strlen($passwordupper)) {
6808 $passwordupper .= PASSWORD_UPPER;
6810 $passwordnonalphanum = PASSWORD_NONALPHANUM;
6811 while ($nonalphanum > strlen($passwordnonalphanum)) {
6812 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
6815 // Now mix and shuffle it all
6816 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
6817 substr(str_shuffle ($passwordupper), 0, $upper) .
6818 substr(str_shuffle ($passworddigits), 0, $digits) .
6819 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
6820 substr(str_shuffle ($passwordlower .
6821 $passwordupper .
6822 $passworddigits .
6823 $passwordnonalphanum), 0 , $additional));
6826 return substr ($password, 0, $maxlen);
6830 * Given a float, prints it nicely.
6831 * Localized floats must not be used in calculations!
6833 * @param float $flaot The float to print
6834 * @param int $places The number of decimal places to print.
6835 * @param bool $localized use localized decimal separator
6836 * @return string locale float
6838 function format_float($float, $decimalpoints=1, $localized=true) {
6839 if (is_null($float)) {
6840 return '';
6842 if ($localized) {
6843 return number_format($float, $decimalpoints, get_string('decsep'), '');
6844 } else {
6845 return number_format($float, $decimalpoints, '.', '');
6850 * Converts locale specific floating point/comma number back to standard PHP float value
6851 * Do NOT try to do any math operations before this conversion on any user submitted floats!
6853 * @param string $locale_float locale aware float representation
6855 function unformat_float($locale_float) {
6856 $locale_float = trim($locale_float);
6858 if ($locale_float == '') {
6859 return null;
6862 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
6864 return (float)str_replace(get_string('decsep'), '.', $locale_float);
6868 * Given a simple array, this shuffles it up just like shuffle()
6869 * Unlike PHP's shuffle() this function works on any machine.
6871 * @param array $array The array to be rearranged
6872 * @return array
6874 function swapshuffle($array) {
6876 srand ((double) microtime() * 10000000);
6877 $last = count($array) - 1;
6878 for ($i=0;$i<=$last;$i++) {
6879 $from = rand(0,$last);
6880 $curr = $array[$i];
6881 $array[$i] = $array[$from];
6882 $array[$from] = $curr;
6884 return $array;
6888 * Like {@link swapshuffle()}, but works on associative arrays
6890 * @param array $array The associative array to be rearranged
6891 * @return array
6893 function swapshuffle_assoc($array) {
6895 $newarray = array();
6896 $newkeys = swapshuffle(array_keys($array));
6898 foreach ($newkeys as $newkey) {
6899 $newarray[$newkey] = $array[$newkey];
6901 return $newarray;
6905 * Given an arbitrary array, and a number of draws,
6906 * this function returns an array with that amount
6907 * of items. The indexes are retained.
6909 * @param array $array ?
6910 * @param ? $draws ?
6911 * @return ?
6912 * @todo Finish documenting this function
6914 function draw_rand_array($array, $draws) {
6915 srand ((double) microtime() * 10000000);
6917 $return = array();
6919 $last = count($array);
6921 if ($draws > $last) {
6922 $draws = $last;
6925 while ($draws > 0) {
6926 $last--;
6928 $keys = array_keys($array);
6929 $rand = rand(0, $last);
6931 $return[$keys[$rand]] = $array[$keys[$rand]];
6932 unset($array[$keys[$rand]]);
6934 $draws--;
6937 return $return;
6941 * microtime_diff
6943 * @param string $a ?
6944 * @param string $b ?
6945 * @return string
6946 * @todo Finish documenting this function
6948 function microtime_diff($a, $b) {
6949 list($a_dec, $a_sec) = explode(' ', $a);
6950 list($b_dec, $b_sec) = explode(' ', $b);
6951 return $b_sec - $a_sec + $b_dec - $a_dec;
6955 * Given a list (eg a,b,c,d,e) this function returns
6956 * an array of 1->a, 2->b, 3->c etc
6958 * @param array $list ?
6959 * @param string $separator ?
6960 * @todo Finish documenting this function
6962 function make_menu_from_list($list, $separator=',') {
6964 $array = array_reverse(explode($separator, $list), true);
6965 foreach ($array as $key => $item) {
6966 $outarray[$key+1] = trim($item);
6968 return $outarray;
6972 * Creates an array that represents all the current grades that
6973 * can be chosen using the given grading type. Negative numbers
6974 * are scales, zero is no grade, and positive numbers are maximum
6975 * grades.
6977 * @param int $gradingtype ?
6978 * return int
6979 * @todo Finish documenting this function
6981 function make_grades_menu($gradingtype) {
6982 $grades = array();
6983 if ($gradingtype < 0) {
6984 if ($scale = get_record('scale', 'id', - $gradingtype)) {
6985 return make_menu_from_list($scale->scale);
6987 } else if ($gradingtype > 0) {
6988 for ($i=$gradingtype; $i>=0; $i--) {
6989 $grades[$i] = $i .' / '. $gradingtype;
6991 return $grades;
6993 return $grades;
6997 * This function returns the nummber of activities
6998 * using scaleid in a courseid
7000 * @param int $courseid ?
7001 * @param int $scaleid ?
7002 * @return int
7003 * @todo Finish documenting this function
7005 function course_scale_used($courseid, $scaleid) {
7007 global $CFG;
7009 $return = 0;
7011 if (!empty($scaleid)) {
7012 if ($cms = get_course_mods($courseid)) {
7013 foreach ($cms as $cm) {
7014 //Check cm->name/lib.php exists
7015 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
7016 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
7017 $function_name = $cm->modname.'_scale_used';
7018 if (function_exists($function_name)) {
7019 if ($function_name($cm->instance,$scaleid)) {
7020 $return++;
7027 // check if any course grade item makes use of the scale
7028 $return += count_records('grade_items', 'courseid', $courseid, 'scaleid', $scaleid);
7030 // check if any outcome in the course makes use of the scale
7031 $return += count_records_sql("SELECT COUNT(*)
7032 FROM {$CFG->prefix}grade_outcomes_courses goc,
7033 {$CFG->prefix}grade_outcomes go
7034 WHERE go.id = goc.outcomeid
7035 AND go.scaleid = $scaleid
7036 AND goc.courseid = $courseid");
7038 return $return;
7042 * This function returns the nummber of activities
7043 * using scaleid in the entire site
7045 * @param int $scaleid ?
7046 * @return int
7047 * @todo Finish documenting this function. Is return type correct?
7049 function site_scale_used($scaleid,&$courses) {
7051 global $CFG;
7053 $return = 0;
7055 if (!is_array($courses) || count($courses) == 0) {
7056 $courses = get_courses("all",false,"c.id,c.shortname");
7059 if (!empty($scaleid)) {
7060 if (is_array($courses) && count($courses) > 0) {
7061 foreach ($courses as $course) {
7062 $return += course_scale_used($course->id,$scaleid);
7066 return $return;
7070 * make_unique_id_code
7072 * @param string $extra ?
7073 * @return string
7074 * @todo Finish documenting this function
7076 function make_unique_id_code($extra='') {
7078 $hostname = 'unknownhost';
7079 if (!empty($_SERVER['HTTP_HOST'])) {
7080 $hostname = $_SERVER['HTTP_HOST'];
7081 } else if (!empty($_ENV['HTTP_HOST'])) {
7082 $hostname = $_ENV['HTTP_HOST'];
7083 } else if (!empty($_SERVER['SERVER_NAME'])) {
7084 $hostname = $_SERVER['SERVER_NAME'];
7085 } else if (!empty($_ENV['SERVER_NAME'])) {
7086 $hostname = $_ENV['SERVER_NAME'];
7089 $date = gmdate("ymdHis");
7091 $random = random_string(6);
7093 if ($extra) {
7094 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
7095 } else {
7096 return $hostname .'+'. $date .'+'. $random;
7102 * Function to check the passed address is within the passed subnet
7104 * The parameter is a comma separated string of subnet definitions.
7105 * Subnet strings can be in one of three formats:
7106 * 1: xxx.xxx.xxx.xxx/xx
7107 * 2: xxx.xxx
7108 * 3: xxx.xxx.xxx.xxx-xxx //a range of IP addresses in the last group.
7109 * Code for type 1 modified from user posted comments by mediator at
7110 * {@link http://au.php.net/manual/en/function.ip2long.php}
7112 * @param string $addr The address you are checking
7113 * @param string $subnetstr The string of subnet addresses
7114 * @return bool
7116 function address_in_subnet($addr, $subnetstr) {
7118 $subnets = explode(',', $subnetstr);
7119 $found = false;
7120 $addr = trim($addr);
7122 foreach ($subnets as $subnet) {
7123 $subnet = trim($subnet);
7124 if (strpos($subnet, '/') !== false) { /// type 1
7125 list($ip, $mask) = explode('/', $subnet);
7126 $mask = 0xffffffff << (32 - $mask);
7127 $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
7128 } else if (strpos($subnet, '-') !== false) {/// type 3
7129 $subnetparts = explode('.', $subnet);
7130 $addrparts = explode('.', $addr);
7131 $subnetrange = explode('-', array_pop($subnetparts));
7132 if (count($subnetrange) == 2) {
7133 $lastaddrpart = array_pop($addrparts);
7134 $found = ($subnetparts == $addrparts &&
7135 $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
7137 } else { /// type 2
7138 $found = (strpos($addr, $subnet) === 0);
7141 if ($found) {
7142 break;
7145 return $found;
7149 * This function sets the $HTTPSPAGEREQUIRED global
7150 * (used in some parts of moodle to change some links)
7151 * and calculate the proper wwwroot to be used
7153 * By using this function properly, we can ensure 100% https-ized pages
7154 * at our entire discretion (login, forgot_password, change_password)
7156 function httpsrequired() {
7158 global $CFG, $HTTPSPAGEREQUIRED;
7160 if (!empty($CFG->loginhttps)) {
7161 $HTTPSPAGEREQUIRED = true;
7162 $CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
7163 $CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
7165 // change theme URLs to https
7166 theme_setup();
7168 } else {
7169 $CFG->httpswwwroot = $CFG->wwwroot;
7170 $CFG->httpsthemewww = $CFG->themewww;
7175 * For outputting debugging info
7177 * @uses STDOUT
7178 * @param string $string ?
7179 * @param string $eol ?
7180 * @todo Finish documenting this function
7182 function mtrace($string, $eol="\n", $sleep=0) {
7184 if (defined('STDOUT')) {
7185 fwrite(STDOUT, $string.$eol);
7186 } else {
7187 echo $string . $eol;
7190 flush();
7192 //delay to keep message on user's screen in case of subsequent redirect
7193 if ($sleep) {
7194 sleep($sleep);
7198 //Replace 1 or more slashes or backslashes to 1 slash
7199 function cleardoubleslashes ($path) {
7200 return preg_replace('/(\/|\\\){1,}/','/',$path);
7203 function zip_files ($originalfiles, $destination) {
7204 //Zip an array of files/dirs to a destination zip file
7205 //Both parameters must be FULL paths to the files/dirs
7207 global $CFG;
7209 //Extract everything from destination
7210 $path_parts = pathinfo(cleardoubleslashes($destination));
7211 $destpath = $path_parts["dirname"]; //The path of the zip file
7212 $destfilename = $path_parts["basename"]; //The name of the zip file
7213 $extension = $path_parts["extension"]; //The extension of the file
7215 //If no file, error
7216 if (empty($destfilename)) {
7217 return false;
7220 //If no extension, add it
7221 if (empty($extension)) {
7222 $extension = 'zip';
7223 $destfilename = $destfilename.'.'.$extension;
7226 //Check destination path exists
7227 if (!is_dir($destpath)) {
7228 return false;
7231 //Check destination path is writable. TODO!!
7233 //Clean destination filename
7234 $destfilename = clean_filename($destfilename);
7236 //Now check and prepare every file
7237 $files = array();
7238 $origpath = NULL;
7240 foreach ($originalfiles as $file) { //Iterate over each file
7241 //Check for every file
7242 $tempfile = cleardoubleslashes($file); // no doubleslashes!
7243 //Calculate the base path for all files if it isn't set
7244 if ($origpath === NULL) {
7245 $origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
7247 //See if the file is readable
7248 if (!is_readable($tempfile)) { //Is readable
7249 continue;
7251 //See if the file/dir is in the same directory than the rest
7252 if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
7253 continue;
7255 //Add the file to the array
7256 $files[] = $tempfile;
7259 //Everything is ready:
7260 // -$origpath is the path where ALL the files to be compressed reside (dir).
7261 // -$destpath is the destination path where the zip file will go (dir).
7262 // -$files is an array of files/dirs to compress (fullpath)
7263 // -$destfilename is the name of the zip file (without path)
7265 //print_object($files); //Debug
7267 if (empty($CFG->zip)) { // Use built-in php-based zip function
7269 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7270 //rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
7271 $zipfiles = array();
7272 $start = strlen($origpath)+1;
7273 foreach($files as $file) {
7274 $tf = array();
7275 $tf[PCLZIP_ATT_FILE_NAME] = $file;
7276 $tf[PCLZIP_ATT_FILE_NEW_FULL_NAME] = substr($file, $start);
7277 $zipfiles[] = $tf;
7279 //create the archive
7280 $archive = new PclZip(cleardoubleslashes("$destpath/$destfilename"));
7281 if (($list = $archive->create($zipfiles) == 0)) {
7282 notice($archive->errorInfo(true));
7283 return false;
7286 } else { // Use external zip program
7288 $filestozip = "";
7289 foreach ($files as $filetozip) {
7290 $filestozip .= escapeshellarg(basename($filetozip));
7291 $filestozip .= " ";
7293 //Construct the command
7294 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7295 $command = 'cd '.escapeshellarg($origpath).$separator.
7296 escapeshellarg($CFG->zip).' -r '.
7297 escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
7298 //All converted to backslashes in WIN
7299 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7300 $command = str_replace('/','\\',$command);
7302 Exec($command);
7304 return true;
7307 function unzip_file ($zipfile, $destination = '', $showstatus = true) {
7308 //Unzip one zip file to a destination dir
7309 //Both parameters must be FULL paths
7310 //If destination isn't specified, it will be the
7311 //SAME directory where the zip file resides.
7313 global $CFG;
7315 //Extract everything from zipfile
7316 $path_parts = pathinfo(cleardoubleslashes($zipfile));
7317 $zippath = $path_parts["dirname"]; //The path of the zip file
7318 $zipfilename = $path_parts["basename"]; //The name of the zip file
7319 $extension = $path_parts["extension"]; //The extension of the file
7321 //If no file, error
7322 if (empty($zipfilename)) {
7323 return false;
7326 //If no extension, error
7327 if (empty($extension)) {
7328 return false;
7331 //Clear $zipfile
7332 $zipfile = cleardoubleslashes($zipfile);
7334 //Check zipfile exists
7335 if (!file_exists($zipfile)) {
7336 return false;
7339 //If no destination, passed let's go with the same directory
7340 if (empty($destination)) {
7341 $destination = $zippath;
7344 //Clear $destination
7345 $destpath = rtrim(cleardoubleslashes($destination), "/");
7347 //Check destination path exists
7348 if (!is_dir($destpath)) {
7349 return false;
7352 //Check destination path is writable. TODO!!
7354 //Everything is ready:
7355 // -$zippath is the path where the zip file resides (dir)
7356 // -$zipfilename is the name of the zip file (without path)
7357 // -$destpath is the destination path where the zip file will uncompressed (dir)
7359 $list = null;
7361 if (empty($CFG->unzip)) { // Use built-in php-based unzip function
7363 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7364 $archive = new PclZip(cleardoubleslashes("$zippath/$zipfilename"));
7365 if (!$list = $archive->extract(PCLZIP_OPT_PATH, $destpath,
7366 PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename',
7367 PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $destpath)) {
7368 if (!empty($showstatus)) {
7369 notice($archive->errorInfo(true));
7371 return false;
7374 } else { // Use external unzip program
7376 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7377 $redirection = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? '' : ' 2>&1';
7379 $command = 'cd '.escapeshellarg($zippath).$separator.
7380 escapeshellarg($CFG->unzip).' -o '.
7381 escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
7382 escapeshellarg($destpath).$redirection;
7383 //All converted to backslashes in WIN
7384 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7385 $command = str_replace('/','\\',$command);
7387 Exec($command,$list);
7390 //Display some info about the unzip execution
7391 if ($showstatus) {
7392 unzip_show_status($list,$destpath);
7395 return true;
7398 function unzip_cleanfilename ($p_event, &$p_header) {
7399 //This function is used as callback in unzip_file() function
7400 //to clean illegal characters for given platform and to prevent directory traversal.
7401 //Produces the same result as info-zip unzip.
7402 $p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
7403 $p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
7404 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7405 $p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
7406 $p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
7407 } else {
7408 //Add filtering for other systems here
7409 // BSD: none (tested)
7410 // Linux: ??
7411 // MacosX: ??
7413 $p_header['filename'] = cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
7414 return 1;
7417 function unzip_show_status ($list,$removepath) {
7418 //This function shows the results of the unzip execution
7419 //depending of the value of the $CFG->zip, results will be
7420 //text or an array of files.
7422 global $CFG;
7424 if (empty($CFG->unzip)) { // Use built-in php-based zip function
7425 $strname = get_string("name");
7426 $strsize = get_string("size");
7427 $strmodified = get_string("modified");
7428 $strstatus = get_string("status");
7429 echo "<table width=\"640\">";
7430 echo "<tr><th class=\"header\" scope=\"col\">$strname</th>";
7431 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strsize</th>";
7432 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strmodified</th>";
7433 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strstatus</th></tr>";
7434 foreach ($list as $item) {
7435 echo "<tr>";
7436 $item['filename'] = str_replace(cleardoubleslashes($removepath).'/', "", $item['filename']);
7437 print_cell("left", s($item['filename']));
7438 if (! $item['folder']) {
7439 print_cell("right", display_size($item['size']));
7440 } else {
7441 echo "<td>&nbsp;</td>";
7443 $filedate = userdate($item['mtime'], get_string("strftimedatetime"));
7444 print_cell("right", $filedate);
7445 print_cell("right", $item['status']);
7446 echo "</tr>";
7448 echo "</table>";
7450 } else { // Use external zip program
7451 print_simple_box_start("center");
7452 echo "<pre>";
7453 foreach ($list as $item) {
7454 echo s(str_replace(cleardoubleslashes($removepath.'/'), '', $item)).'<br />';
7456 echo "</pre>";
7457 print_simple_box_end();
7462 * Returns most reliable client address
7464 * @return string The remote IP address
7466 function getremoteaddr() {
7467 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
7468 return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
7470 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
7471 return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
7473 if (!empty($_SERVER['REMOTE_ADDR'])) {
7474 return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
7476 return '';
7480 * Cleans a remote address ready to put into the log table
7482 function cleanremoteaddr($addr) {
7483 $originaladdr = $addr;
7484 $matches = array();
7485 // first get all things that look like IP addresses.
7486 if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER)) {
7487 return '';
7489 $goodmatches = array();
7490 $lanmatches = array();
7491 foreach ($matches as $match) {
7492 // print_r($match);
7493 // check to make sure it's not an internal address.
7494 // the following are reserved for private lans...
7495 // 10.0.0.0 - 10.255.255.255
7496 // 172.16.0.0 - 172.31.255.255
7497 // 192.168.0.0 - 192.168.255.255
7498 // 169.254.0.0 -169.254.255.255
7499 $bits = explode('.',$match[0]);
7500 if (count($bits) != 4) {
7501 // weird, preg match shouldn't give us it.
7502 continue;
7504 if (($bits[0] == 10)
7505 || ($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
7506 || ($bits[0] == 192 && $bits[1] == 168)
7507 || ($bits[0] == 169 && $bits[1] == 254)) {
7508 $lanmatches[] = $match[0];
7509 continue;
7511 // finally, it's ok
7512 $goodmatches[] = $match[0];
7514 if (!count($goodmatches)) {
7515 // perhaps we have a lan match, it's probably better to return that.
7516 if (!count($lanmatches)) {
7517 return '';
7518 } else {
7519 return array_pop($lanmatches);
7522 if (count($goodmatches) == 1) {
7523 return $goodmatches[0];
7525 //Commented out following because there are so many, and it clogs the logs MDL-13544
7526 //error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
7528 // We need to return something, so return the first
7529 return array_pop($goodmatches);
7533 * file_put_contents is only supported by php 5.0 and higher
7534 * so if it is not predefined, define it here
7536 * @param $file full path of the file to write
7537 * @param $contents contents to be sent
7538 * @return number of bytes written (false on error)
7540 if(!function_exists('file_put_contents')) {
7541 function file_put_contents($file, $contents) {
7542 $result = false;
7543 if ($f = fopen($file, 'w')) {
7544 $result = fwrite($f, $contents);
7545 fclose($f);
7547 return $result;
7552 * The clone keyword is only supported from PHP 5 onwards.
7553 * The behaviour of $obj2 = $obj1 differs fundamentally
7554 * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
7555 * created, in PHP 5 $obj1 is referenced. To create a copy
7556 * in PHP 5 the clone keyword was introduced. This function
7557 * simulates this behaviour for PHP < 5.0.0.
7558 * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
7560 * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
7561 * Found a better implementation (more checks and possibilities) from PEAR:
7562 * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
7564 * @param object $obj
7565 * @return object
7567 if(!check_php_version('5.0.0')) {
7568 // the eval is needed to prevent PHP 5 from getting a parse error!
7569 eval('
7570 function clone($obj) {
7571 /// Sanity check
7572 if (!is_object($obj)) {
7573 user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
7574 return;
7577 /// Use serialize/unserialize trick to deep copy the object
7578 $obj = unserialize(serialize($obj));
7580 /// If there is a __clone method call it on the "new" class
7581 if (method_exists($obj, \'__clone\')) {
7582 $obj->__clone();
7585 return $obj;
7588 // Supply the PHP5 function scandir() to older versions.
7589 function scandir($directory) {
7590 $files = array();
7591 if ($dh = opendir($directory)) {
7592 while (($file = readdir($dh)) !== false) {
7593 $files[] = $file;
7595 closedir($dh);
7597 return $files;
7600 // Supply the PHP5 function array_combine() to older versions.
7601 function array_combine($keys, $values) {
7602 if (!is_array($keys) || !is_array($values) || count($keys) != count($values)) {
7603 return false;
7605 reset($values);
7606 $result = array();
7607 foreach ($keys as $key) {
7608 $result[$key] = current($values);
7609 next($values);
7611 return $result;
7617 * This function will make a complete copy of anything it's given,
7618 * regardless of whether it's an object or not.
7619 * @param mixed $thing
7620 * @return mixed
7622 function fullclone($thing) {
7623 return unserialize(serialize($thing));
7628 * This function expects to called during shutdown
7629 * should be set via register_shutdown_function()
7630 * in lib/setup.php .
7632 * Right now we do it only if we are under apache, to
7633 * make sure apache children that hog too much mem are
7634 * killed.
7637 function moodle_request_shutdown() {
7639 global $CFG;
7641 // initially, we are only ever called under apache
7642 // but check just in case
7643 if (function_exists('apache_child_terminate')
7644 && function_exists('memory_get_usage')
7645 && ini_get_bool('child_terminate')) {
7646 if (empty($CFG->apachemaxmem)) {
7647 $CFG->apachemaxmem = 25000000; // default 25MiB
7649 if (memory_get_usage() > (int)$CFG->apachemaxmem) {
7650 trigger_error('Mem usage over $CFG->apachemaxmem: marking child for reaping.');
7651 @apache_child_terminate();
7654 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
7655 if (defined('MDL_PERFTOLOG')) {
7656 $perf = get_performance_info();
7657 error_log("PERF: " . $perf['txt']);
7659 if (defined('MDL_PERFINC')) {
7660 $inc = get_included_files();
7661 $ts = 0;
7662 foreach($inc as $f) {
7663 if (preg_match(':^/:', $f)) {
7664 $fs = filesize($f);
7665 $ts += $fs;
7666 $hfs = display_size($fs);
7667 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
7668 , NULL, NULL, 0);
7669 } else {
7670 error_log($f , NULL, NULL, 0);
7673 if ($ts > 0 ) {
7674 $hts = display_size($ts);
7675 error_log("Total size of files included: $ts ($hts)");
7682 * If new messages are waiting for the current user, then return
7683 * Javascript code to create a popup window
7685 * @return string Javascript code
7687 function message_popup_window() {
7688 global $USER;
7690 $popuplimit = 30; // Minimum seconds between popups
7692 if (!defined('MESSAGE_WINDOW')) {
7693 if (isset($USER->id) and !isguestuser()) {
7694 if (!isset($USER->message_lastpopup)) {
7695 $USER->message_lastpopup = 0;
7697 if ((time() - $USER->message_lastpopup) > $popuplimit) { /// It's been long enough
7698 if (get_user_preferences('message_showmessagewindow', 1) == 1) {
7699 if (count_records_select('message', 'useridto = \''.$USER->id.'\' AND timecreated > \''.$USER->message_lastpopup.'\'')) {
7700 $USER->message_lastpopup = time();
7701 return '<script type="text/javascript">'."\n//<![CDATA[\n openpopup('/message/index.php', 'message',
7702 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0);\n//]]>\n</script>";
7709 return '';
7712 // Used to make sure that $min <= $value <= $max
7713 function bounded_number($min, $value, $max) {
7714 if($value < $min) {
7715 return $min;
7717 if($value > $max) {
7718 return $max;
7720 return $value;
7723 function array_is_nested($array) {
7724 foreach ($array as $value) {
7725 if (is_array($value)) {
7726 return true;
7729 return false;
7733 *** get_performance_info() pairs up with init_performance_info()
7734 *** loaded in setup.php. Returns an array with 'html' and 'txt'
7735 *** values ready for use, and each of the individual stats provided
7736 *** separately as well.
7739 function get_performance_info() {
7740 global $CFG, $PERF, $rcache;
7742 $info = array();
7743 $info['html'] = ''; // holds userfriendly HTML representation
7744 $info['txt'] = me() . ' '; // holds log-friendly representation
7746 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
7748 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
7749 $info['txt'] .= 'time: '.$info['realtime'].'s ';
7751 if (function_exists('memory_get_usage')) {
7752 $info['memory_total'] = memory_get_usage();
7753 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
7754 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
7755 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
7758 if (function_exists('memory_get_peak_usage')) {
7759 $info['memory_peak'] = memory_get_peak_usage();
7760 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
7761 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
7764 $inc = get_included_files();
7765 //error_log(print_r($inc,1));
7766 $info['includecount'] = count($inc);
7767 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
7768 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
7770 if (!empty($PERF->dbqueries)) {
7771 $info['dbqueries'] = $PERF->dbqueries;
7772 $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
7773 $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
7776 if (!empty($PERF->logwrites)) {
7777 $info['logwrites'] = $PERF->logwrites;
7778 $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
7779 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
7782 if (!empty($PERF->profiling) && $PERF->profiling) {
7783 require_once($CFG->dirroot .'/lib/profilerlib.php');
7784 $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
7787 if (function_exists('posix_times')) {
7788 $ptimes = posix_times();
7789 if (is_array($ptimes)) {
7790 foreach ($ptimes as $key => $val) {
7791 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
7793 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
7794 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
7798 // Grab the load average for the last minute
7799 // /proc will only work under some linux configurations
7800 // while uptime is there under MacOSX/Darwin and other unices
7801 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
7802 list($server_load) = explode(' ', $loadavg[0]);
7803 unset($loadavg);
7804 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
7805 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
7806 $server_load = $matches[1];
7807 } else {
7808 trigger_error('Could not parse uptime output!');
7811 if (!empty($server_load)) {
7812 $info['serverload'] = $server_load;
7813 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
7814 $info['txt'] .= "serverload: {$info['serverload']} ";
7817 if (isset($rcache->hits) && isset($rcache->misses)) {
7818 $info['rcachehits'] = $rcache->hits;
7819 $info['rcachemisses'] = $rcache->misses;
7820 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
7821 "{$rcache->hits}/{$rcache->misses}</span> ";
7822 $info['txt'] .= 'rcache: '.
7823 "{$rcache->hits}/{$rcache->misses} ";
7825 $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
7826 return $info;
7829 function apd_get_profiling() {
7830 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
7834 * Delete directory or only it's content
7835 * @param string $dir directory path
7836 * @param bool $content_only
7837 * @return bool success, true also if dir does not exist
7839 function remove_dir($dir, $content_only=false) {
7840 if (!file_exists($dir)) {
7841 // nothing to do
7842 return true;
7844 $handle = opendir($dir);
7845 $result = true;
7846 while (false!==($item = readdir($handle))) {
7847 if($item != '.' && $item != '..') {
7848 if(is_dir($dir.'/'.$item)) {
7849 $result = remove_dir($dir.'/'.$item) && $result;
7850 }else{
7851 $result = unlink($dir.'/'.$item) && $result;
7855 closedir($handle);
7856 if ($content_only) {
7857 return $result;
7859 return rmdir($dir); // if anything left the result will be false, noo need for && $result
7863 * Function to check if a directory exists and optionally create it.
7865 * @param string absolute directory path (must be under $CFG->dataroot)
7866 * @param boolean create directory if does not exist
7867 * @param boolean create directory recursively
7869 * @return boolean true if directory exists or created
7871 function check_dir_exists($dir, $create=false, $recursive=false) {
7873 global $CFG;
7875 if (strstr($dir, $CFG->dataroot.'/') === false) {
7876 debugging('Warning. Wrong call to check_dir_exists(). $dir must be an absolute path under $CFG->dataroot ("' . $dir . '" is incorrect)', DEBUG_DEVELOPER);
7879 $status = true;
7881 if(!is_dir($dir)) {
7882 if (!$create) {
7883 $status = false;
7884 } else {
7885 umask(0000);
7886 if ($recursive) {
7887 /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-(
7888 $dir = str_replace('\\', '/', $dir); //windows compatibility
7889 /// We are going to make it recursive under $CFG->dataroot only
7890 /// (will help sites running open_basedir security and others)
7891 $dir = str_replace($CFG->dataroot . '/', '', $dir);
7892 $dirs = explode('/', $dir); /// Extract path parts
7893 /// Iterate over each part with start point $CFG->dataroot
7894 $dir = $CFG->dataroot . '/';
7895 foreach ($dirs as $part) {
7896 if ($part == '') {
7897 continue;
7899 $dir .= $part.'/';
7900 if (!is_dir($dir)) {
7901 if (!mkdir($dir, $CFG->directorypermissions)) {
7902 $status = false;
7903 break;
7907 } else {
7908 $status = mkdir($dir, $CFG->directorypermissions);
7912 return $status;
7915 function report_session_error() {
7916 global $CFG, $FULLME;
7918 if (empty($CFG->lang)) {
7919 $CFG->lang = "en";
7921 // Set up default theme and locale
7922 theme_setup();
7923 moodle_setlocale();
7925 //clear session cookies
7926 if (check_php_version('5.2.0')) {
7927 //PHP 5.2.0
7928 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly);
7929 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly);
7930 } else {
7931 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure);
7932 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure);
7934 //increment database error counters
7935 if (isset($CFG->session_error_counter)) {
7936 set_config('session_error_counter', 1 + $CFG->session_error_counter);
7937 } else {
7938 set_config('session_error_counter', 1);
7940 redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
7945 * Detect if an object or a class contains a given property
7946 * will take an actual object or the name of a class
7947 * @param mix $obj Name of class or real object to test
7948 * @param string $property name of property to find
7949 * @return bool true if property exists
7951 function object_property_exists( $obj, $property ) {
7952 if (is_string( $obj )) {
7953 $properties = get_class_vars( $obj );
7955 else {
7956 $properties = get_object_vars( $obj );
7958 return array_key_exists( $property, $properties );
7963 * Detect a custom script replacement in the data directory that will
7964 * replace an existing moodle script
7965 * @param string $urlpath path to the original script
7966 * @return string full path name if a custom script exists
7967 * @return bool false if no custom script exists
7969 function custom_script_path($urlpath='') {
7970 global $CFG;
7972 // set default $urlpath, if necessary
7973 if (empty($urlpath)) {
7974 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
7977 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
7978 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
7979 return false;
7982 // replace wwwroot with the path to the customscripts folder and clean path
7983 $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
7985 // remove the query string, if any
7986 if (($strpos = strpos($scriptpath, '?')) !== false) {
7987 $scriptpath = substr($scriptpath, 0, $strpos);
7990 // remove trailing slashes, if any
7991 $scriptpath = rtrim($scriptpath, '/\\');
7993 // append index.php, if necessary
7994 if (is_dir($scriptpath)) {
7995 $scriptpath .= '/index.php';
7998 // check the custom script exists
7999 if (file_exists($scriptpath)) {
8000 return $scriptpath;
8001 } else {
8002 return false;
8007 * Wrapper function to load necessary editor scripts
8008 * to $CFG->editorsrc array. Params can be coursei id
8009 * or associative array('courseid' => value, 'name' => 'editorname').
8010 * @uses $CFG
8011 * @param mixed $args Courseid or associative array.
8013 function loadeditor($args) {
8014 global $CFG;
8015 include($CFG->libdir .'/editorlib.php');
8016 return editorObject::loadeditor($args);
8020 * Returns whether or not the user object is a remote MNET user. This function
8021 * is in moodlelib because it does not rely on loading any of the MNET code.
8023 * @param object $user A valid user object
8024 * @return bool True if the user is from a remote Moodle.
8026 function is_mnet_remote_user($user) {
8027 global $CFG;
8029 if (!isset($CFG->mnet_localhost_id)) {
8030 include_once $CFG->dirroot . '/mnet/lib.php';
8031 $env = new mnet_environment();
8032 $env->init();
8033 unset($env);
8036 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
8040 * Checks if a given plugin is in the list of enabled enrolment plugins.
8042 * @param string $auth Enrolment plugin.
8043 * @return boolean Whether the plugin is enabled.
8045 function is_enabled_enrol($enrol='') {
8046 global $CFG;
8048 // use the global default if not specified
8049 if ($enrol == '') {
8050 $enrol = $CFG->enrol;
8052 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
8056 * This function will search for browser prefereed languages, setting Moodle
8057 * to use the best one available if $SESSION->lang is undefined
8059 function setup_lang_from_browser() {
8061 global $CFG, $SESSION, $USER;
8063 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
8064 // Lang is defined in session or user profile, nothing to do
8065 return;
8068 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
8069 return;
8072 /// Extract and clean langs from headers
8073 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
8074 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
8075 $rawlangs = explode(',', $rawlangs); // Convert to array
8076 $langs = array();
8078 $order = 1.0;
8079 foreach ($rawlangs as $lang) {
8080 if (strpos($lang, ';') === false) {
8081 $langs[(string)$order] = $lang;
8082 $order = $order-0.01;
8083 } else {
8084 $parts = explode(';', $lang);
8085 $pos = strpos($parts[1], '=');
8086 $langs[substr($parts[1], $pos+1)] = $parts[0];
8089 krsort($langs, SORT_NUMERIC);
8091 $langlist = get_list_of_languages();
8093 /// Look for such langs under standard locations
8094 foreach ($langs as $lang) {
8095 $lang = strtolower(clean_param($lang.'_utf8', PARAM_SAFEDIR)); // clean it properly for include
8096 if (!array_key_exists($lang, $langlist)) {
8097 continue; // language not allowed, try next one
8099 if (file_exists($CFG->dataroot .'/lang/'. $lang) or file_exists($CFG->dirroot .'/lang/'. $lang)) {
8100 $SESSION->lang = $lang; /// Lang exists, set it in session
8101 break; /// We have finished. Go out
8104 return;
8108 ////////////////////////////////////////////////////////////////////////////////
8110 function is_newnav($navigation) {
8111 if (is_array($navigation) && !empty($navigation['newnav'])) {
8112 return true;
8113 } else {
8114 return false;
8119 * Checks whether the given variable name is defined as a variable within the given object.
8120 * @note This will NOT work with stdClass objects, which have no class variables.
8121 * @param string $var The variable name
8122 * @param object $object The object to check
8123 * @return boolean
8125 function in_object_vars($var, $object) {
8126 $class_vars = get_class_vars(get_class($object));
8127 $class_vars = array_keys($class_vars);
8128 return in_array($var, $class_vars);
8132 * Returns an array without repeated objects.
8133 * This function is similar to array_unique, but for arrays that have objects as values
8135 * @param unknown_type $array
8136 * @param unknown_type $keep_key_assoc
8137 * @return unknown
8139 function object_array_unique($array, $keep_key_assoc = true) {
8140 $duplicate_keys = array();
8141 $tmp = array();
8143 foreach ($array as $key=>$val) {
8144 // convert objects to arrays, in_array() does not support objects
8145 if (is_object($val)) {
8146 $val = (array)$val;
8149 if (!in_array($val, $tmp)) {
8150 $tmp[] = $val;
8151 } else {
8152 $duplicate_keys[] = $key;
8156 foreach ($duplicate_keys as $key) {
8157 unset($array[$key]);
8160 return $keep_key_assoc ? $array : array_values($array);
8164 * Returns the language string for the given plugin.
8166 * @param string $plugin the plugin code name
8167 * @param string $type the type of plugin (mod, block, filter)
8168 * @return string The plugin language string
8170 function get_plugin_name($plugin, $type='mod') {
8171 $plugin_name = '';
8173 switch ($type) {
8174 case 'mod':
8175 $plugin_name = get_string('modulename', $plugin);
8176 break;
8177 case 'blocks':
8178 $plugin_name = get_string('blockname', "block_$plugin");
8179 if (empty($plugin_name) || $plugin_name == '[[blockname]]') {
8180 if (($block = block_instance($plugin)) !== false) {
8181 $plugin_name = $block->get_title();
8182 } else {
8183 $plugin_name = "[[$plugin]]";
8186 break;
8187 case 'filter':
8188 $plugin_name = trim(get_string('filtername', $plugin));
8189 if (empty($plugin_name) or ($plugin_name == '[[filtername]]')) {
8190 $textlib = textlib_get_instance();
8191 $plugin_name = $textlib->strtotitle($plugin);
8193 break;
8194 default:
8195 $plugin_name = $plugin;
8196 break;
8199 return $plugin_name;
8203 * Is a userid the primary administrator?
8205 * @param $userid int id of user to check
8206 * @return boolean
8208 function is_primary_admin($userid){
8209 $primaryadmin = get_admin();
8211 if($userid == $primaryadmin->id){
8212 return true;
8213 }else{
8214 return false;
8218 // vim:autoindent:expandtab:shiftwidth=4:tabstop=4:tw=140: